Tags: programación

Cómo esnifar el tráfico en dispositivos móviles

16 Jul 2013

Hacer sniffing del tráfico de red no es únicamente una solución para hacking; de hecho, a la hora de desarrollar aplicaciones móviles, nos encontramos en muchos casos con la necesidad de conocer qué información se está intercambiando entre el dispositivo móvil y el servidor. Es bastante habitual que la app que nos está funcionando correctamente contra un servidor local, o uno de nuestra LAN corporativa, de repente no lo hace al apuntar a un servidor en internet, al colocar un SSL delante…

Por todo esto, una técnica que podemos utilizar es interceptar el tráfico de red entrante/saliente con el dispositivo móvil, para poder analizar las cabeceras, el cuerpo de los mensajes, etc. Para ello, en este post vamos a utilizar una potente suite de seguridad, Burpsuite.

En primer lugar, descargaremos Burpsuite. La última versión que he utilizado para este post es la versión 1.5 Free. Es una aplicación Java, por lo que nos funcionará en Windows, Linux y Mac Os X; para ello, previamente deberemos haber descargado una versión de la Java Runtime Environment.

Pulsando sobre el fichero JAR se nos debería abrir la aplicación:

Burpsuite es una suite de seguridad muy completa, pero en este post sólo vamos a ver el funcionamiento de las pestañas “Target” y “Proxy”.

A continuación vamos a crear la configuración de proxy, que posteriormente configuraremos en nuestro dispositivo móvil, asegurando que todo el tráfico del mismo pueda ser interceptado y analizado por Burpsuite. Para ello, pulsamos en Proxy -> Options, y en el apartado de Proxy Listeners, pulsamos en la opción Add, para poder crear una interface de red que esté escuchando a cualquier petición realizada desde el dispositivo móvil. La ventana que se nos abre nos pide un puerto (en mi ejemplo he utilizado el 8081) y seleccionamos la opción “All interfaces“. Deberemos confirmar esta selección al pulsar OK:

Ya tenemos nuestro proxy creado, escuchando por el puerto 8081. En la pestaña Proxy -> Intercept, veremos que por defecto está seleccionada la opción “Intercept is on”. Eso quiere decir que cada petición del móvil va a ser detenida por el proxy; podremos evaluarla, y permitir su ejecución o no pulsando en los botones “Forward” y “Drop”.

Ahora vamos a configurar nuestro dispositivo móvil para que las peticiones de red pasen por el proxy. Es importante que el ordenador donde hemos instalado Burpsuite y el móvil estén conectados a la misma red (wifi, por ejemplo). Otra opción que podemos tener es compartir la conexión de red desde el ordenador por wifi, y conectarnos con el móvil a la misma. En mi caso, tanto el ordenador (un Macbook Pro) como el móvil (iPhone) están conectados a la misma red wifi. También necesitaremos conocer la IP de nuestro ordenador (en Mac, es sencillo de comprobar en las Preferencias de Red; en mi caso, tengo la IP 192.168.2.122:

Ahora voy a configurar el proxy en el iPhone (esto es válido para cualquier otro tipo de dispositivo móvil Android, Windows Phone, BlackBerry, etc., pero los settings de red estarán en otro menú). En iPhone, acudo al menú Ajustes -> Wifi -> y pulso en la flecha azul al lado de la red wifi a la que estoy conectado, entrando en la pantalla de ajustes de la conexión. Abajo del todo se puede configurar un servidor proxy. Seleccionamos “Manual”, e introducimos la configuración del proxy Burpsuite, con la IP de nuestro ordenador y el puerto que hemos seleccionado. En mi caso, la IP 192.168.2.122 y el puerto 8081:

Y en principio, con esto es suficiente. Tenemos un proxy escuchando por un puerto, y hemos configurado dicho proxy en nuestro dispositivo móvil. Ahora procedemos a ejecutar la aplicación que queremos esnifar. Como ejemplo, abro la app de Marca, y vemos en Burpsuite una de las peticiones que hace la aplicación al arrancar:

Podremos analizar las cabeceras, ver el tráfico en la pestaña “Target”, etc. En definitiva, tener información en tiempo real de qué está ocurriendo entre el dispositivo móvil y el servidor de backend.

Espero que os sea de ayuda, y sed buenos!

The Nerd’s life: Development department (Spanish version)

19 Jun 2013

En el universo nerd (o lo que en Español denominados friki) hay diferentes temas, sensaciones y sentimientos que una persona externa a este universo difícilmente puede entender. El que la última versión de un framework despierte más atención que la final de la liga de campeones es algo que sólo un auténtico friki puede entender, pero cuidado, como dijo Bill Gates; “Nunca te rías de un Nerd, puede convertirse en tu jefe

Nosotros, nos consideramos (y estamos bastante orgullosos de ello) frikis de la informática y por tanto, también tenemos nuestro peculiar sentido del humor.

Hace un año lanzábamos algunos posts que puede que el resto del universo no entendiera. Trataban sobre cómo mejorar la productividad de la empresa a la hora de programar, intentando no interrumpir a tus compañeros, ya que en programación hay un estado (medio comatoso) en el que un friki puede entrar llegado un punto de concentración llamado nirvana zona.



Cuando un friki está en la zona, su productividad / líneas de código escritas puede verse incrementada en un 188% (según datos de la universidad de Mata-chusetts), por tanto, cuando que un compañero tiene pinta de estar en ella…piénsatelo dos veces antes de preguntarle que piensa de tu nueva manera de comentar el código o podrías despertar al Gollum que todo programador lleva dentro.

De OrangeHRM a OpenERP con OpenETL

18 Oct 2012

Después de mis últimos post Migración de datos entre distintas instancias de OpenERP usando OpenETL y Carga de datos en OpenERP usando OpenETL y aprovechando que estamos realizando una migración en la empresa desde OrangeHRM a OpenERP que mejor forma de cerrar el círculo que presentar en forma de post el proceso de migración que hemos seguido.

El escenario:

Para migrar los datos hemos partido de los archivos .csv que se generan desde OrangeHRM. Como dichos archivos contienen información de la empresa no los voy a adjuntar con el post, simplemente me limitaré a describir los campos. También partimos de un OpenERP que ya tiene cargados datos como países, provincias, y empresa. Para realizar las llamadas al xmlrpc he usado al usuario admin, el cual ya pertenece a la empresa. De esta forma el campo company_id se ha ajustado automáticamente.

Los archivos .csv contienen los siguientes campos:

Empleados.csv:

  • empID: Identificación del empleado en Orange.
  • lastName: Apellidos del empleado.
  • firstName: Primer nombre del empleado.
  • middleName: Segundo nombre del empleado.
  • street1: Dirección del empleado.
  • street2: Campo de apoyo para street1.
  • city: Municipio.
  • state: Provincia.
  • zip: Código postal.
  • gender: Género.
  • birthDate: Fecha de nacimiento.
  • ssn: Número de la seguridad social.
  • workStation: Departamento al que pertenece el empleado.

Los campos middleName, street1, street2, city, state, zip, workStation,birthDate pueden estar vacíos en el archivo .csv, por lo que hay que controlar estos casos.


dptos.csv:

  • workStation: Nombre del departamento en el sistema.


cargos.csv:

  • empId: Identificación del empleado en Orange.
  • empStatus: Cargo del empleado en la empresa.

El modelo de datos relacionados afectado de OpenERP se presenta a continuación. Lo he simplificado bastante y sólo he puesto los campos de los objetos que se van a cargar con los valores de los .csv.

Si te alejas de la pantalla y te pones bizco, verás a un tio bailando. En realidad es el diagrama de clases simplificado de objetos OpenERP.

Si te alejas de la pantalla y te pones bizco, verás a un tio bailando. En realidad es el diagrama de clases simplificado de objetos OpenERP.

A tener en cuenta:

Observando el modelo de datos se puede ver que muchos objetos están relacionados entre sí. Esto implica que si queremos cargar los empleados, antes tenemos que tener cargados en el sistema las direcciones. Este mismo comportamiento nos sucede con los departamentos, puestos de trabajo, etc.

Para solucionar este inconveniente he usado subtareas de OpenETL. El archivo subjob_example.py contiene un ejemplo para el uso de subtareas con OpenETL. El funcionamiento es bastante sencillo. Simplemente en vez de ejecutar la tarea que queremos convertir en subtarea, crearemos un nuevo componente de tipo subtarea con ella. Después a la tarea padre le pasamos como parámetro dicha subtarea.

En el código:

job_ = openetl.job([csv_in1,datos_ajustados,openobject_out2])  # Para poder relacionar direcciones con personas, las direcciones deben estar cargadas
subjob = openetl.component.transform.subjob(job_)              # en el sistema. Las cargo previamente en una subtarea.

job1=openetl.job([subjob_cargos,subjob_dptos,subjob_paises,subjob,csv_in1,datos_ajustados,openobject_out1])

job1.run()

Las subtareas implicadas son son:

  • subjob_cargos: Carga las categorías de los empleados.
  • subjob_dptos: Carga los departamentos de la empresa.
  • subjo_paises: Realiza correspondencia de países de OpenERP con Orange.
  • subjob: Carga las direcciones de los empleados.

Y los diagramas de cada subtarea:

Diagramas de subjob_cargos y subjob_dptos.

Diagramas de subjob_cargos y subjob_dptos.

Diagramas de subjob_paises y subjob.

Diagramas de subjob_paises y subjob.

Para relacionar los objetos de las subtareas con la carga final, la de los empleados, he usado un pequeño truco. Vamos a fijarnos en la lista de categorías de empleados (subjob_cargos).

Al leer las categorías desde el csv inicial, las he pasado por una transformación que ejecuta una función (preprocess_cargos):

lista_cargos = {}
def preprocess_cargos(self, channels):
    for trans in channels['carga_cargos']:
        for d in trans:
            lista_cargos[d['empId']] = d['empStatus']
    return None

pres_cargos=openetl.component.transform.map({},preprocess_cargos)

Dicha función lo único que hace es cargar en un diccionario una relación empId-empStatus, es decir, relaciona id de empleado con su categoría.

Más adelante en el código, al realizar la carga de los empleados, consulto dicho diccionario:

def preprocess(self, channels):
    cdict = {}
    for trans in channels['modificacion']:
        for d in trans:
            .
            .
            .
            # Ajuste de cargo
            d['cargo'] = lista_cargos[d['empId']]

    return {'resultado':cdict}

Y por último en el mapeado del objeto, antes de cargarlo en OpenERP:

openobject_out1 = openetl.component.output.openobject_out(
     ooconnector,
     'hr.employee',
     {
      .
      .
      .
      'job_id':'cargo',
      }
    )

En el diagrama también aparece una paso previo por el componente unique. Dicho componente quita los elementos duplicados antes de cargarlos en el sistema. Hay un ejemplo de uso de dicho componente en el fichero unique_csv_data.py.

Otra cosa interesante de esta migración es como se han mapeado los datos del csv a los objetos. El fichero join_example.py contiene un ejemplo que usa map_keys. Dicho ejemplo está muy bien y funciona siempre y cuando se usen componentes “openetl.component.input.data” definidos en el propio archivo de script. El problema es que cuando se lee un archivo de csv no se está usando una entrada “estática”, sino secuencial. De modo que el map_keys es ignorado. La solución en este caso ha sido pasar por parámetro un map_key vacío y realizar el mapeo de datos desde el propio código de la función preprocess.

En el caso de países:

pre_paises=openetl.component.transform.map({},preprocess_paises)
def preprocess(self, channels):
    cdict = {}
    for trans in channels['modificacion']:
        for d in trans:

           .
           .
           .
            # Ajuste de paises
            if d['state'] == "Santo Domingo":
                d['state'] = lista_paises[62]  # Codigo de Republica Dominicana
            elif d['state'] == "Distrito Nacional":
                d['state'] = lista_paises[62]
            else:
                d['state'] = lista_paises[69] # Codigo de Espagna
            .
            .
            .    

    return {'resultado':cdict}

El resultado:

Como en mis anteriores post presento el código completo de la solución obtenida. Evidentemente este script de migración cubre nuestras necesidades concretas, pero es fácil adaptarlo si se necesitan migrar datos diferentes. También se podría haber realizado un script de migración atacando directamente a la base de datos de Orange, aunque por seguir con el ejemplo planteado en el primer post de OpenETL se han usado archivos .csv. En cualquier caso OpenETL también contiene conectores para consultas SQL. El archivo sql_in_example.py contiene un ejemplo con el que se podrían sustituir las llamadas a los csv con consultas sql.

import sys
sys.path.append('..')

import openetl

#===============================================================================
# Conectores
#===============================================================================
fileconnector_orange=openetl.connector.localfile('/home/carlos/Escritorio/Orange/Empleados.csv')
fileconnector_orange_dptos=openetl.connector.localfile('/home/carlos/Escritorio/Orange/dptos.csv') # Con tratamiento previo
fileconnector_orange_cargos=openetl.connector.localfile('/home/carlos/Escritorio/Orange/cargos.csv') # Con tratamiento previo
ooconnector = openetl.connector.openobject_connector('http://localhost:8069', 'master_viavansi', 'admin', 'admin', con_type='xmlrpc')

#===============================================================================
# Componentes
#===============================================================================
csv_in1= openetl.component.input.csv_in(fileconnector_orange,name='Datos de Orange')
csv_in_dptos= openetl.component.input.csv_in(fileconnector_orange_dptos,name='Departamentos')
csv_in_cargos= openetl.component.input.csv_in(fileconnector_orange_cargos,name='Cargos')

openobject_out1 = openetl.component.output.openobject_out(
     ooconnector,
     'hr.employee',
     {
      'name':'name_csv',
      'ssnid':'ssn',
      'gender':'gender',
      'birthday':'birthDate',
      'address_home_id':'name_csv', # Nombre de la relacion
      'department_id':'workStation',
      'job_id':'cargo',
      }
    )

openobject_out2 = openetl.component.output.openobject_out(
     ooconnector,
     'res.partner.address',
     {
      'name':'name_csv',
      'street':'street1',
      'street2':'street2',
      'zip':'zip',
      'city':'city',
      'country_id':'state',
      }
    )

openobject_out3 = openetl.component.output.openobject_out(
     ooconnector,
     'hr.department',
     {
      'name':'workStation',
      }
    )

# Soporte para carga de datos de cargo de empleado. El Diccionario se carga en subtarea previa
lista_cargos = {}
openobject_out4 = openetl.component.output.openobject_out(
     ooconnector,
     'hr.job',
     {
      'name':'empStatus',
      }
    )

def preprocess_cargos(self, channels):
    for trans in channels['carga_cargos']:
        for d in trans:
            lista_cargos[d['empId']] = d['empStatus']
    return None

pres_cargos=openetl.component.transform.map({},preprocess_cargos)

# Soporte para carga de datos de paises. El Diccionario se carga en subtarea previa
lista_paises = {}

openobject_in1 = openetl.component.input.openobject_in(
                 ooconnector,'res.country',
                 fields=['id','name'],
                 )

def preprocess_paises(self, channels):
    for trans in channels['carga_paises']:
        for d in trans:
            lista_paises[d['id']] = d['name']
    return None

pre_paises=openetl.component.transform.map({},preprocess_paises)

# Soporte transformaciones y componentes

def preprocess(self, channels):
    cdict = {}
    for trans in channels['modificacion']:
        for d in trans:
            # name: no existia,lo creo yo con la suma de los campos 

            if d['middleName'] == "":  # En OpenERP, no se separan los campos, hay un unico campo name
                d["name_csv"] = d["firstName"] + str(" ")+ d["lastName"]
            else:
                d["name_csv"] = d["firstName"] + str(" ")+ d["middleName"] +str(" ")+ d["lastName"]

            if d['gender'] == "M":     # Adaptacion de nomencaltura de datos de Orange a OpenERP
                d['gender'] = 'male'
            else:
                d['gender'] ='female'

            # Ajuste de paises
            if d['state'] == "Santo Domingo":
                d['state'] = lista_paises[62]  # Codigo de Republica Dominicana
            elif d['state'] == "Distrito Nacional":
                d['state'] = lista_paises[62]
            else:
                d['state'] = lista_paises[69] # Codigo de Espagna

            # Ajuste de cargo
            d['cargo'] = lista_cargos[d['empId']]

    return {'resultado':cdict}            

datos_ajustados=openetl.component.transform.map({},preprocess)  # Como leo un flujo de datos, no hay key_map. key_maps es para diccionarios

#===============================================================================
# Transiciones, Definicion de trabajo y ejecucion. Operaciones de Carga
#===============================================================================

log_cargos=openetl.component.transform.logger(name='Log de cargos')
unique_job = openetl.component.transform.unique()
openetl.transition(csv_in_cargos,pres_cargos,channel_destination='carga_cargos')
openetl.transition(pres_cargos,log_cargos)
openetl.transition(csv_in_cargos,unique_job)
openetl.transition(unique_job,openobject_out4)
job_cargos=openetl.job([csv_in_cargos,unique_job,openobject_out4,log_cargos])
subjob_cargos = openetl.component.transform.subjob(job_cargos)  

unique = openetl.component.transform.unique()
log_dptos=openetl.component.transform.logger(name='Log departamentos')

openetl.transition(csv_in_dptos,unique)
openetl.transition(unique,log_dptos,channel_source='main')
openetl.transition(unique,openobject_out3)
job_dptos=openetl.job([log_dptos,openobject_out3])
subjob_dptos = openetl.component.transform.subjob(job_dptos)  

openetl.transition(openobject_in1,pre_paises, channel_destination='carga_paises')
job_paises = openetl.job([openobject_in1,pre_paises])
subjob_paises = openetl.component.transform.subjob(job_paises)  

openetl.transition(csv_in1,datos_ajustados, channel_destination='modificacion') # Leo datos aplicando preprocesamiento
openetl.transition(csv_in1,openobject_out2) # Direcciones
openetl.transition(csv_in1,openobject_out1) # Personas

job_ = openetl.job([csv_in1,datos_ajustados,openobject_out2])  # Para poder relacionar direcciones con personas, las direcciones deben estar cargadas
subjob = openetl.component.transform.subjob(job_)              # en el sistema. Las cargo previamente en una subtarea.

job1=openetl.job([subjob_cargos,subjob_dptos,subjob_paises,subjob,csv_in1,datos_ajustados,openobject_out1])
job1.run()

Con esto concluye la parte técnica del post. Creo que OpenETL es una tecnología muy interesante, que permite realizar trabajos de ETL de forma bastante cómoda e intuitiva. También os comento que he echado en falta algo más de documentación técnica sobre OpenETL, ya que he tenido que recurrir al código fuente de muchos componentes, transformaciones, etc. para averiguar que es lo que hacían.

A pesar de ello la línea de aprendizaje de esta tecnología es bastante sencilla una vez que sabes que hay que hacer, y se pueden lograr grandes cosas en poco tiempo.

Para finalizar os comentaré que mi impresión final sobre OpenETL es muy buena. No sólo porque se adapte perfectamente a operaciones ETL sobre OpenERP, sino porque tiene un amplio abanico de conectores (sql, facebook, xmlrpc,csv, gdoc, gcalendar, etc) que permiten usar OpenETL en muchos proyectos con distintas tecnologías.

Migración de datos entre diferentes instancias de OpenERP usando OpenETL

25 Sep 2012

En mi anterior post realicé una introducción a OpenETL. También desarrollé un ejemplo de carga de datos desde una archivo .csv a OpenERP.

En este post voy a profundizar un poco más, realizando una migración de datos de OpenERP a OpenERP en los cuales hay tablas relacionadas.

El escenario

Nuestro entorno de migración constará de las siguientes características:

  • BD_inicial contiene los datos que queremos migrar. Los datos serán los clientes y proveedores con sus direcciones.
  • No todos los clientes o proveedores tienen una dirección asociada, por lo que hay que controlar la excepción.
  • Para simplificar el ejemplo voy a migrar sólo el contenido de los campos name, title y partner_id, siendo title el campo que contiene la relación con la tabla res_partner_title y partner_id el campo relacionado con la tabla res_partner.
Diagrama de relación de clases simplificado de res_partner_address con res_partner y res_partner_titulo

Relación simplificada de relaciones de objetos res_partner_address, res_partner_title y res_partner.

A tener en cuenta

Cuando migras un contenido desde OpenERP a un archivo .csv el sistema suele funcionar sin complicaciones. Sin embargo cuando se realiza la migración de OpenERP a OpenERP es fácil obtener excepciones tal como

File "/usr/lib/python2.6/xmlrpclib.py", line 838, in close
raise Fault(**self._stack[0])
xmlrpclib.Fault: <Fault 'bool' object has no attribute 'lower': 'Traceback (most recent call last):n  File … … … …  /openerp/osv/orm.py", line 1380, in process_linessn    res = line[i].lower() not in ('0', 'false', 'off')nAttributeError: 'bool' object has no attribute 'lower'n'>

Esto ocurre porque OpenERP al leer un campo sin valor le asigna por defecto un booleano inicializado a False.

En el ejemplo data_map.py después de leer los valores de ejemplo desde el .csv el autor realiza una transformación de los mismos antes de mostrarlos en el log. Basándose en ese ejemplo, es fácil inicializar los campos con valores adecuados:

def preprocess(self, channels):
    cdict = {}
    for trans in channels['modificacion']:
        for d in trans:
            if d["name"] == False:
                d["name"] = ""
            cdict[d['id']] = d
    return {'resultado':cdict}

Otra cosa que también puede producir muchos quebraderos de cabeza es que en los campos relacionados no se va a poner el id de la tupla relacionada, sino el valor de la misma. Nuestra función quedaría así:

def preprocess(self, channels):
    cdict = {}
    for trans in channels['modificacion']:
        for d in trans:
            if d["title"] == False: # Es una relacion, ej: 'title': [5, 'Sir'] , con res_partner_title
                d["title"] = '' # Si quiero dejarlo sin valor, le dejo las comillas
            else:
                d["title"] = d["title"][1]  # No se coge el 0, que es el id, sino el valor. El id se ajusta automatico :/
            if d["name"] == False:
                d["name"] = ""

            if d["partner_id"] == False:
                d["partner_id"] = ""
            else:
                d["partner_id"] = d["partner_id"][1]

            cdict[d['id']] = d
    return {'resultado':cdict}

El resultado

El código completo, con conectores, componentes, transiciones, etc. se muestra a continuación. Nótese que la función de procesamiento es llamada desde una transición openetl.component.transform.map(map_keys,preprocess), en la que se pasa también un parámetro map. Hay más ejemplos parecidos en data_map.py y m2m_into_oo.py.

#!/usr/bin/python

import sys
sys.path.append('..')

import openetl
from openetl import transformer

# Conectores
ooconnector_in = openetl.connector.openobject_connector('http://localhost:8069', 'BD_inicial', 'admin', 'admin', con_type='xmlrpc')
ooconnector_out = openetl.connector.openobject_connector('http://localhost:8069', 'BD_final', 'admin', 'admin', con_type='xmlrpc')

# Componentes
openobject_in1 = openetl.component.input.openobject_in(
                 ooconnector_in,'res.partner.address',
                 fields=['id','title','name','partner_id'],
                 )

openobject_in2 = openetl.component.input.openobject_in(
                 ooconnector_in,'res.partner',
                 fields=['id','name'],
                 )

openobject_out1 = openetl.component.output.openobject_out(
     ooconnector_out,
     'res.partner.address',
     {'name':'name','title':'title','partner_id':'partner_id'}
    )

openobject_out2 = openetl.component.output.openobject_out(
     ooconnector_out,
     'res.partner',
     {'name':'name'}
    )

log=openetl.component.transform.logger(name='Recien leido:Read Partner File ')

# Soporte transformaciones

map_keys = {'main': {
    'name': "resultado[main['id']]['name']",
    'title': "resultado[main['id']]['title']",
    'partner_id': "resultado[main['id']]['partner_id']",
}}

def preprocess(self, channels):
    cdict = {}
    for trans in channels['modificacion']:
        for d in trans:
            if d["title"] == False: # Es una relacion, ej: 'title': [5, 'Sir'] , con res_partner_title
                d["title"] = '' # Si quiero dejarlo sin valor, le dejo las comillas
            else:
                d["title"] = d["title"][1]  # No se coge el 0, que es el id, sino el valor. El id se ajusta automatico :/
            if d["name"] == False:
                d["name"] = ""

            if d["partner_id"] == False:
                d["partner_id"] = ""
            else:
                d["partner_id"] = d["partner_id"][1]

            cdict[d['id']] = d
    return {'resultado':cdict}               

map=openetl.component.transform.map(map_keys,preprocess)

# Transiciones
tran1=openetl.transition(openobject_in1,map, channel_destination='modificacion')
tran3=openetl.transition(openobject_in1,log)

tran_res_partner01=openetl.transition(openobject_in2, openobject_out2)

tran4=openetl.transition(openobject_in1, map)
tran4=openetl.transition(map, openobject_out1)

# Definicion de trabajo y ejecucion
job1=openetl.job([openobject_in1,map,openobject_out1,openobject_in2,openobject_out2])
job1.run()

Este código funciona y realiza la migración de datos sin ningún problema siempre que en las tablas relacionadas no haya ningún dato con igual campo “valor relacionado” repetido. ¿Y qué pasa si el “valor relacionado” sí está repetido? Lo que ocurre en este caso es que el sistema creará la relación con la tupla con id más pequeño. Para corregir esta situación bastaría con añadir alguna condición más a la función preprocess, ayudarnos de alguna otra función en python auxiliar, etc. Si se diera ese caso los ejemplos sql_in_example.py, csv_diff_example.py, join_example.py, podrían servir como base en función del tratamiento que quisiéramos hacer.

OpenERP data load using OpenETL

08 Ago 2012

Introduction:

OpenETL is a library in python for OpenERP S.A Data Migration. This tool allows us to perform all typical ETL operations (extract, transform and load) with the added value of being very well integrated with OpenERP.

The website of the library is https://launchpad.net/openetl

There is also a graphical interface packaged as a module (etl_interface) for OpenERP. This module is an extra add-ons. Although since the interface is easier to handle, this post will focus on the use of python library.

Installing:

To download the library you must install bazaar.

The specific command:

bzr branch lp:openetl

After downloading the openerp branch, copy the folder openetl/lib/openetl to your system libraries folder. In my case /usr/lib/python2.6/.

Although in my eclipse with PyDev I already have set the route, Eclipse Indigo seems not to “catch” the first library. To refresh the accessible libraries in eclipse, go to PyDevInterpreter – Python, click on Restore Defaults (if we already had configured the system) and then click on Apply.

Performance:

The library is divided into work, components, connectors, and transitions.

  • Jobs: Processes that can run, pause and stop.
  • Components: Inputs, outputs and transformation components. They allow us to get data and store them into external systems.The transformation components will be those that fit the data before the final charge.
  • Connectors: The connectors define connections with the external systems. They are used by the components. The current version of the library has connectors to treat local files, Openobjects, different databases (postgres, mysql, oracle), URLs (http, ftp, https, gopher), xmlrpc web services , SugarCRM, other google services (GDocs , GCalendar, gblog) and facebook.
  • Transitions:  Transitions are the flow the data between component is passing through.

The programmer must define as many input conectors as outputs are needed, at least one component for each connector, and a minimal transition to pass data from one component to another. Connectors are linked with the components, so for writting data into the component, they are written into the external system.

Instance:

We have a OrangeHRM system installed in the company with the HR tabs we want to migrate to OpenERP. The HR staff has exported the data to a .Csv format and asks us to perform the OpenERP data loading.

What we have to do is create two connectors, one will connect locally against the .csv file and the other type of XMLRPC will connect to OpenERP. Then define the components that are going to store the information, create the transitions (one for writing data to the final component and one for sorting the data) which are going to run and finally we launch the task. In the OpenObject component, we’ll define the csv fields mapping to the erp object fields . Transitions are executed sequentially as you have defined them in the .py .

Diagrama de carga de datos con OpenETL con 2 transiciones y 2 conectores

Diagrama de carga de datos con OpenETL con 2 transiciones y 2 conectores

The associated source code:

import sys
sys.path.append('..')

import openetl

# Conectors
fileconnector_orange=openetl.connector.localfile('/home/carlos/Escritorio/csvRaquel/DatosRRHHOrangeHRM.csv')

ooconnector = openetl.connector.openobject_connector('http://localhost:8069', 'testProject', 'admin', 'admin', con_type='xmlrpc')

# Components
csv_in1= openetl.component.input.csv_in(fileconnector_orange,name='Datos de Orange')

oo_out_employee = openetl.component.output.openobject_out(
     ooconnector,
     'hr.employee',
     {'name':'firstName'}
    )

sort1=openetl.component.transform.sort('firstName')

# Transitions

tran1=openetl.transition(csv_in1,sort1)

tran2=openetl.transition(sort1, oo_out_employee)

# Work and run definition
job1=openetl.job([csv_in1,sort1,oo_out_employee])
job1.run()

To make the example easier, I have simplified the number of fields to load from the .csv, fields with related tables, etc.. But the load can be as much complicated as necessary so that all data is migrated correctly.

In the folder openetlexamples there are examples of all the things we will need in the migrating data process , going from examples with multiple inputs and outputs (csv_in_out.py) to data load with related tables (m2m_into_oo.py). This example is quite interesting, as it reads users and csv files groups and load them directly into OpenERP. Let’s note also the example of migrating data from SugarCRM, facebook, GCalendar, etc..

Processes that can run, pause and stop.

JBoss Seam: render an external xhtml

02 Dic 2011

Las 20 mejores respuestas cuando una aplicación no funciona

11 May 2010

A continuación, las mejores respuestas de un desarrollador ante un fallo en una de sus aplicaciones:

20. “Esto es raro …”

19. “Esto nunca había pasado antes.”

18. “Ayer funcionaba.”

17. “¿Cómo es esto posible?”

16. “Debe ser un problema del hardware.”

15. “¿Qué pusistes para que esto petara?”

14. “Algo está mal en tus datos.”

13. “Pues, ¡no he tocado este módulo en semanas!”

12. “Tienes que tener una versión errónea.”

11. “Sólo es una desafortunada coincidencia.”

10. “¡No lo puedo testear todo!”

9. “ESTO no puede ser la causa de AQUELLO”.

8. “Funcionar funciona, lo que pasa que no ha sido probado”.

7. “Alguien ha tenido que tocar mi código”.

6. “¿Has mirado si tienes virus en tu sistema?”

5. “A pesar de que esto no funciona, ¿cómo te sientes?”

4. “Esta versión no está hecha para este sistema.”

3. “¿Por qué tienes que hacerlo así?”

2. “¿Dónde estabas cuando la aplicación reventó?”

Y el número uno de las respuestas ante fallos de aplicaciones:

1. “En mi máquina funciona”

Traducción libre de The Top 20 replies by programmers when their programs do not work

Curiosidad: Estoy seguro de que nos olvidamos de algo…(Los problemas de la sinceridad)

27 Ene 2010

Que pasaría si al hacer una entrega de software al cliente le decimos:

Estoy seguro de que nos olvidamos de algo, y sé que tenemos algunos conflictos pendientes. Al mismo tiempo, necesitamos la cobertura de una versión real, y en su conjunto tiene muy buen aspecto. Hemos corregido unos cuantos conflictos en los últimos días, y siempre tendremos las versiones 2.6.30.x (parches).

Pues este es precisamente el mensaje con el Linus Torvalds dio a conocer la versión 2.6.30 del Kernel de Linux!! (el mensaje original en: http://lkml.org/lkml/2009/6/9/710). Sería impensable que le dijéramos una frase similar a un cliente.

Salvando los posibles problemas con clientes que “entren en pánico” debido a este derroche de sinceridad, y ahora que está tan de moda las políticas de calidad, objetivos, métricas, … Nuestro objetivo es que la calidad de nuestras aplicaciones esté tan demostrada, que en una situación puntual le podamos decir a un cliente una frase tan sincera como ésta, sin que el cliente sienta ningún tipo de angustia.

Visteme despacio que tengo prisa!

10 Mar 2009

Los programadores se enfrentan a una paradoja de valores básicos. Todos los desarrolladores con algunos años de experiencia saben que desordenes previos son causas de retraso. Y sin embargo todos los desarrolladores sienten que la presión les hace dejar mal algunas cosas para conseguir alcanzar las fechas de entrega. En resumen, no se toman el tiempo necesario para ir rápido.

Los verdaderos profesionales saben que la segunda parte de la paradoja está mal. No llegarás a la fecha haciendo mal las cosas. Es más, hacer las cosas mal te retrasará instantáneamente, y te forzará a incumplir la fecha de entrega. La única manera de llegar a la fecha -la única manera de ir rápido- es mantener el código tan limpio como sea posible durante todo el tiempo.

Parrafos del libro “Clean Code”,  leido en el blog de José Manuel Beas

Programar es divertido, entregar es tu trabajo

25 Ene 2007

Un abstract,  en traducion, mas o menos libre, de algo escrito por Chuck Jazdzewski:

  • Nunca dejes de aprender. Nunca dejes de estudiar.
  • La comunicacion es critica. Aprende a expresarte. Oralmente y por escrito. Transmite confianza. Aprende a comunicarte con otros tecnicos, pero tambien con clientes, con jefes,  gente de marketing, con gente que no sabe ni quiere saber el argot tecnico… Aprende a expresarte en ingles, al menos a nivel tecnico.
  • Promete menos de lo que crees poder hacer, y da mas de lo que has prometido.
  • Admite cuando te equivocas. Y hazlo saber, no lo escondas.
  • Si no ha sido testeado, no funciona. Asumir lo contrario es un error.
  • Programar es divertido, pero no es tu trabajo, entregar si lo es. Si hace falta documentar, hazlo, es tu trabajo.

(El blog de Chuck: http://www.removingalldoubt.com/)