Páginas

domingo, 2 de diciembre de 2018

FPGAs libres

¿Qué son las FPGA?

FPGA (field-programmable gate array) son un tipo de chips que contienen una matriz de puertas lógicas donde que podemos configurar:
  • La E/S
  • Las interconexiones entre las puertas lógicas de la matriz
  • La operación de cada una de las puertas lógicas, AND, OR, NOT, XOR y Biestables. No es que en cada elemento de la matriz exista N puertas lógicas combiandas de una manera ingeniosa, lo que se almacena en cada elemento de la matriz es una micromemoria que asocia a cada dirección de entrada una salida. Así si en un elemento de la matriz queremos guardar una puerta AND tendríamos una memoria en la que en la dirección 00 hay almacenado un cero, en la dirección 01 hay almacenado un 0, en la dirección 10 hay almacenado un 0 y en la dirección 11 hay almacenado un 1. La tabla booleana sería:
   DIR. ENTRADA | DATO SALIDA
   00                      | 0
   01                      | 0
   10                      | 0
   11                      | 1

Actualmente estos dispositivos están experimentando un gran auge desde que Clifford Woolf en 2014 liberó sus herramientas open source (Verilog) para reprogramar las FPGA ICE40 de Lattice, lo que ha dado un gran impulso a esta tecnología (hasta la fecha ultra protegida), se están empezando a liberar estos chips, y se está desarrollando una cultura de hardware libre similar a la que GNU supuso en el mundo del software libre de los años 80.

Las ventajas de poder programar internamente un chip son muchas:
  • Cualquier dispositivo con estos chips pasan a ser multipropósito, reconfigurables y actualizable. Se minimiza la obsolescencia y la necesidad de contar con N dispositivos. Un dispositivo puede reprogramarse en milisegundos para ser otra cosa.
  • Se abre un ecosistema de hardware libre (https://opencores.org/) donde cualquiera puede desarrollar o vender 'librerías' de circuitos que permitan configurar un dispositivo para cualquier finalidad: contadores, codificadores, decodificadores, encriptador, desencriptador, procesamiento de imágenes, procesamiento de sonido, etc.
  • Ya existen 'traductores' que permiten pasar desde lenguajes de alto nivel (C/C++) al lenguaje HDL (Hardware Description Languaje) para programar esos circuitos desde un lenguaje conocido. Existen también proyectos como IceStorm que permiten generar HDL mediante una interfac gráfica. El código HDL se traduce a un chorro de bits (bitestream) que es lo que se usa para grabar las FPGA.
  • La liberación de las FPGA pueden suponer una revolución en automatización industrial, IoT, Smart Cities, redes neuronales, procesamiento de lenguaje natural, algoritmos genéticos, robótica, reconocimiento de patrones en el big data, etc.
¿Qué ventajas tienen las FPGA frente a los microcontroladores?

Desde hace unos años tenemos una revolución en marcha con microcontroladores como Arduino o Raspberry Pi (10 euros) que permiten desarrollar pequeños programas asociados a sensores/actuadores en el mundo de IoT y la robótica. Estos dispositivos ha supuesto una auténtica revolución tanto a nivel comercial como a nivel educativo.

Las FPGA ofrecen varias ventajas importantes frente a los microprocesadores:
  • Velocidad. Las FPGAs procesan la información muchísimo mas rápido que los micros actuales, trabajan en velocidades similares a procesadores de niveles superiores. Cualquier micro sigue las instrucciones de un programa escrito en algún tipo de lenguaje ensamblador que es traducido a instrucciones máquina por el microcódigo y ejecutado en el procesador por lo que cualquier instrucción por sencilla que sea implica varios pasos que se ejecutan en varios ciclos de reloj. Una FPGA está 'cableada' para ejecutar ese mismo código sin instrucciones, por lo que es mucho mas rápida. Hoy en día las velocidades de reloj de los procesadores y micros actuales son mas altas que las velocidades de los FPGA pero la inercia del mercado será la de igualar estas velocidades.
  • Tiempo real. Al no depender el funcionamiento del chip de la ejecución de un programa la respuesta a eventos es instantánea. Con un micro debemos recurrir a interrupciones que avisan al procesador que está ejecutando una instrucción de muchos ciclos, que debe atender a otro evento, estas interrupciones son siempre 'escuchadas' al final de una ejecución.
  • Paralelismo. Una FPGA puede programarse para N funcionalidades a la vez. Si la FPGA tiene 64 entradas nada impide que usemos 32 entradas para un procesador de video y 32 entradas para un compresor de audio.
  • Volatilidad. Las FPGA son volátiles, por lo que al desconectar se pierde el circuito por lo que deberemos almacenar el bitstream en algún dispositivo externo que lo cargue de nuevo al arrancar el dispositivo.
  • Diseño de hardware accesible.Con la llegada de las FPGA libres el diseño de hardware se va a universalizar. Los chips actuales son cajas negras, pero desde ahora cualquiera podrá programar compartir y mejorar circuitos con lenguajes como Verilog y HDL.El hardware se genera vía software.
Hoy en día hay muchas empresas realizando inversiones millonarias en el mundo de las FPGA, por ejemplo: Intel, Microsoft y AWS. AWS por ejemplo ofrece FPGAS en la nube donde podemos programar nuestros 'circuitos' y abre el camino a que esos circuitos puedan ser revendidos como librerías a otros interesados.

Referencias:

https://es.wikipedia.org/wiki/Field-programmable_gate_array
https://www.youtube.com/watch?v=K8bM14-R9Ts
https://www.youtube.com/watch?v=GA1lN5dsgao
https://www.youtube.com/watch?v=By8x3gL88T0
https://alhambrabits.com/alhambra/
https://www.arduino.cc/en/Guide/MKRVidor4000
https://alhambrabits.com/software/

sábado, 27 de octubre de 2018

Comando bash para reemplazar una cadena de texto en múltiples ficheros recursivamente

Para reemplazar una cadena de texto en todos los ficheros de un cierto directorio y también el los ficheros de sus subdirectorios con un único comando donde combinamos grep y sed.

Sería el comando:

grep -rl "cadena1" * -R | xargs sed -i 's/cadena1/cadena2/g'

Siendo cadena1 el literal a reemplazar y cadena2 el nuevo literal

Un saludo

sábado, 14 de abril de 2018

Ejemplo de MapReduce con Java sobre HDFS

En esta entrada vamos a realizar una breve descripción de Apache Hadoop, del sistema de archivo distribuído HDFS y del algoritmo de procesamiento MapReduce

1. Introducción a Hadoop.

La utilidad de Hadoop radica en que nos permite procesar rápidamente un volumen inmenso de datos (Big Data), la clave está en que el procesamiento se produce en paralelo en múltiples nodos. Cada nodo procesa parte del trabajo y el resultado de todo ese procesamiento paralelo se combina en el resultado final.

Cuantos mas datos tengamos que procesar y/o cuanto mayor sea la velocidad de respuesta requerida, mas nodos paralelos deberemos tener instalada en la red de servidores Hadoop.

Hadoop se basa en dos 'patas':

1.1) Un sistema de archivos llamado HDFS distribuído en N máquinas o nodos

Ya hemos comentado que Hadoop es eficiente cuando opera sobre ficheros extremadamente grandes (petabytes), estos ficheros se trocean en bloques de 128MB que son almacenados en múltiples nodos de la red de servidores. Para minimizar problemas si un noco cae, cada uno de los trozos está disponible en al menos 3 servidores.

Hay un nodo maestro que gestiona qué trozos forman parte de cada fichero y en qué nodos está replicado. Este nodo maestro está también replicado para evitar caídas del sistema.

El sistema de Archivos HDFS corre sobre Java, por lo que puede ejecutar sobre cualquier arquitectura que cuente con una JVM.

1.2) Un algoritmo de procesamiento map-reduce basado en dos operaciones simples

El algoritmo base de Haddop se llama MapReduce. La base de MapReduce es muy simple:

  • Un algoritmo MapReduce recibe un conjunto de pares <Clave,Valor> y genera pares <Clave,Valor>
  • La operación Map recibe un conjunto de pares <Clave,Valor> y genera pares <Clave,Valor>
  • La operación Reduce recibe un conjunto de pares <Clave,Valor> y genera pares <Clave,Valor>

Supongamos que tenemos una ciudad con una red de miles sensores. Los sensores toman la temperatura de su ubicación cada 10 segundos y la envían a un almacén centralizado, los sensores que son nuestros productores de datos, podrían enviar sus lecturas a un topic de Apache Kafka y ese topic lo tendríamos enganchado a un consumidor HDFS mediante un conector.

Cada sensor enviaría un registro en formato JSON como el siguinte:

{"idsensor":"00000001", fechahora: "20180414214500", "longitud": -122.0829009197085, "latitud": 37.4238253802915, "temp": 11.2}

Si por ejemplo nos piden sacar la temperatura media de cada sensor para cada día del año pasado deberíamos programar la siguiente secuencia de procesos mapreduce:

1.2.1) Partimos de un fichero imaginario fichero-temperaturas-madrid-2017.json

El fichero no es otra cosa sino un conjunto de pares <Clave,Valor> donde las claves son los números de línea "numlinea" y los valores el contenido de cada línea "contenidolinea". Partiríamos pues de algo así:

[
{"numlinea": 1, "contenidolinea": {"idsensor":"00000001", "fechahora": "20180414214500", "longitud": -122.0829009197085, "latitud": 37.4238253802915, "temp": 11.2}},
{"numlinea": 2, "contenidolinea": {"idsensor":"00000001", "fechahora": "20180414214500", "longitud": -122.0829009197085, "latitud": 37.4238253802915, "temp": 11.2}},
{"numlinea": 3, "contenidolinea": {"idsensor":"00000001", "fechahora": "20180414214500", "longitud": -122.0829009197085, "latitud": 37.4238253802915, "temp": 11.2}},
....
{"numlinea": 999, "contenidolinea": {"idsensor":"00000001", "fechahora": "20180414214500", "longitud": -122.0829009197085, "latitud": 37.4238253802915, "temp": 11.2}}
]

1.2.2) Programamos una operación MAP

La operación MAP transformará los pares <numlinea, contenidolinea> en pares <idsensorFecha,temp>, que serían del tipo:

[
{"idsensorFecha":"00000001-20180414", "temp": 11.2},
{"idsensorFecha":"00000001-20180414", "temp": 11.3},
{"idsensorFecha":"00000001-20180414", "temp": 11.4},
....
{"idsensorFecha":"99999999-20180414", "temp": 9.0},
{"idsensorFecha":"99999999-20180414", "temp": 9.2},
{"idsensorFecha":"99999999-20180414", "temp": 9.2},
]

1.2.3) Mezcla y ordenación de la salida del MAP

La salida de la operación MAP realizada en cada trozo del fichero de cada uno de los nodos de Hadoop, se mezcla y ordena en un único fichero que contiene las líneas de todos los ficheros ordenadas por la clave "idsensorFecha" y agrupando las temperaturas en una matriz <idsensorFecha,[temp]>, con la pinta siguiente:

[
{"idsensorFecha":"00000001-20180414", "temperaturas": [{"temp": 11.2},{"temp": 11.3}, ...., {"temp": 11.4}]},
        ....
{"idsensorFecha":"99999999-20180414", "temperaturas": [{"temp": 11.2},{"temp": 11.3}, ...., {"temp": 11.4}]}
]

1.2.4) Programamos una operación REDUCE.

La operación REDUCE recibe pares del tipo <idsensorFecha,[temp]> y genera un pares del tipo <idsensorFecha,tempMedia>, donde hemos sacado la media de todas las temperaturas de un sensor en una cierta fecha, generamos por tanto pares del tipo:

[
{"idsensorFecha":"00000001-20180414", "tempMedia": 13.2},
        ....
{"idsensorFecha":"99999999-20180414", "tempMedia": 17.1}
]

2. Instalar Hadoop

Accedemos a la zona de descargas de Apache Hadoop:

http://hadoop.apache.org/releases.html#Download

Nos bajamos la versión ejecutable mas actualizada, en este caso: 

http://apache.rediris.es/hadoop/common/hadoop-3.1.0/hadoop-3.1.0.tar.gz

Vamos ahora a instalar Hadoop en modo autónomo:

  • Descomprimimos en nuestro $HOME
  • Definimos la variable HADOOP_HOME ajustando su valor en el .profile

HADOOP_HOME=/home/egdepedro/hadoop-3.1.0
export HADOOP_HOME
PATH="$HADOOP_HOME/bin:$PATH"

  • Reiniciamos la sesión o recargamos en bash el contenido del .profile con el comando:

$ . ./.profile

  • Verificamos que todo se ha instalado correctamente lanzando el comando

egdepedro@OBERON:~$ hadoop version
Hadoop 3.1.0
Source code repository https://github.com/apache/hadoop -r 16b70619a24cdcf5d3b0fcf4b58ca77238ccbe6d
Compiled by centos on 2018-03-30T00:00Z
Compiled with protoc 2.5.0
From source with checksum 14182d20c972b3e2105580a1ad6990
This command was run using /home/egdepedro/hadoop-3.1.0/share/hadoop/common/hadoop-common-3.1.0.jar

3. Obtención de datos y definición del ejemplo

Hoy en día muchos organismos y ayuntamientos publican sus datos en una 'iniciativa' conocida como 'Open Data' cuyo objetivo final es que cualquier ciudadano/empresa sea capaz de explotar esos datos de forma productiva. Los datos están siempre convenientemente anonimizados, es decir, no se publica nada sobre nadie que permita identificar al afectado.

Vamos a descargar por ejemplo los datos del Ayuntamiento de Madrid:  https://datos.madrid.es/portal/site/egob/ y descargamos por ejemplo los accidentes de tráfico con bicicleta del 2017. El enlace es: https://datos.madrid.es/egob/catalogo/300110-0-accidentes-bicicleta.csv

Revisando el fichero y limpiando un poco espacios en blanco inútiles he generado el fichero 'accidentes-bicicletas-madrid-2017.csv' que tiene la siguiente estructura:

Fecha;TRAMO HORARIO;Nm Tot Victimas;DISTRITO;Lugar;Numero;Tipo Accidente;Tipo Vehiculo

01/01/2017;DE 6:00 A 6:59;1;ARGANZUELA;CALLE DE TOLEDO;120;CHOQUE CON OBJETO FIJO;BICICLETA           
02/01/2017;DE 21:00 A 21:59;1;SAN BLAS;CALLE DE MEQUINENZA;14;CAÍDA BICICLETA;BICICLETA           
03/01/2017;DE 19:00 A 19:59;1;CENTRO;CALLE DE LA ESCALINATA;8;CAÍDA BICICLETA;BICICLETA           
04/01/2017;DE 21:00 A 21:59;1;CENTRO;CALLE DE LA CAVA DE SAN MIGUEL;13;CAÍDA BICICLETA;BICICLETA           
05/01/2017;DE 8:00 A 8:59;1;MONCLOA-ARAVACA;PUENTE DE LOS FRANCESES;;CAÍDA BICICLETA;BICICLETA           
05/01/2017;DE 12:00 A 12:59;1;LATINA;CARRETERA DE BOADILLA DEL MONTE;27;CAÍDA BICICLETA;BICICLETA         

4. Definimos el caso de uso

Supondremos que alguien nos solicita extraer el 'Número total de muertos por distrito', algo del estilo siguiente:

ARGANZUELA 11
SAN BLAS 15
....

5. Cargamos los datos en una carpeta del HDFS (sistema de ficheros distribuído de Hadoop) que llamaremos 'bicicletas:

egdepedro@OBERON:~/Escritorio$ hadoop fs -mkdir bicicletas
egdepedro@OBERON:~/Escritorio$ hadoop fs -put ./accidentes-bicicletas-madrid-2017.csv bicicletas

Podemos comprobar si se ha subido bien mediante el comando:
 
egdepedro@OBERON:~/Escritorio$ hadoop fs -ls bicicletas
Found 1 items
-rw-r--r--   1 egdepedro egdepedro      83474 2018-04-14 09:59 bicicletas/accidentes-bicicletas-madrid-2017.csv

6. Montamos los programas mapreduce para los dos casos de uso

6.1) Creamos un proyecto Maven y definimos las dependencias con el siguiente pom.xml 

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.casa</groupId>
<artifactId>TestMapReduce</artifactId>
<version>1.0</version>
<properties>
<slf4j.version>1.7.25</slf4j.version>
<proyecto.build.jdk_version>1.8</proyecto.build.jdk_version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<hadoop-common.version>3.1.0</hadoop-common.version>
<hadoop-client.version>3.1.0</hadoop-client.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>${proyecto.build.jdk_version}</source>
<target>${proyecto.build.jdk_version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>${hadoop-common.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${hadoop-client.version}</version>
</dependency>
</dependencies>
</project>

6.2) Para cada cado de uso vamos a crear 3 clases:

Clase Driver. Donde figura el Main que define el trabajo a realizar: 

package org.casa;

import java.io.IOException;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.FileInputFormat;
import org.apache.hadoop.mapred.FileOutputFormat;
import org.apache.hadoop.mapred.JobClient;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.TextInputFormat;
import org.apache.hadoop.mapred.TextOutputFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AccidentesPorDistritoDriver {

private static final Logger LOGGER = LoggerFactory.getLogger(AccidentesPorDistritoDriver.class);

public static void main(String[] args) throws IOException {

LOGGER.info("Se crea el trabajo y su configuración, asignándole un nombre");
final JobClient miClienteHadoop = new JobClient();
final JobConf configTrabajo = new JobConf(AccidentesPorDistritoDriver.class);
configTrabajo.setJobName("AccidentesPorDistrito");

LOGGER.info("Se definen los tipos de los pares <clave,valor> de salida <texto, entero>");
configTrabajo.setOutputKeyClass(Text.class);
configTrabajo.setOutputValueClass(IntWritable.class);

LOGGER.info("Se define las clases mapper y reducer");
configTrabajo.setMapperClass(AccidentesPorDistritoMapper.class);
configTrabajo.setReducerClass(AccidentesPorDistritoReducer.class);

LOGGER.info("Se definen los tipos de los ficheros de entrada y salida (en ambos casos ficheros de texto plano)");
configTrabajo.setInputFormat(TextInputFormat.class);
configTrabajo.setOutputFormat(TextOutputFormat.class);

LOGGER.info("Se definen los directorios de entrada/salida");
FileInputFormat.setInputPaths(configTrabajo, new Path(args[0]));
FileOutputFormat.setOutputPath(configTrabajo, new Path(args[1]));

LOGGER.info("Cargamos el trabajo en el cliente Hadoop");
miClienteHadoop.setConf(configTrabajo);

try {
LOGGER.info("Ejecutamos el trabajo");
JobClient.runJob(configTrabajo);
} catch (Exception e) {
e.printStackTrace();
}
}
}

Clase Mapper. Donde se implementa la rutina de mapeo.

package org.casa;

import java.io.IOException;
import java.util.StringTokenizer;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.MapReduceBase;
import org.apache.hadoop.mapred.Mapper;
import org.apache.hadoop.mapred.OutputCollector;
import org.apache.hadoop.mapred.Reporter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Procesamos las líneas de un fichero CSV que contienen:
 * 
 *   fecha;tramo-horario;num-victimas;distrito;lugar;numero;tipo-accidente;tipo-vehiculo
 *   
 * Extraemos de cada línea el par <distrito, num-victimas>
 */
public class AccidentesPorDistritoMapper extends MapReduceBase implements Mapper<LongWritable, Text, Text, IntWritable> {

private static final Logger LOGGER = LoggerFactory.getLogger(AccidentesPorDistritoMapper.class);

public void map(LongWritable numLinea, Text txtLinea, OutputCollector <Text, IntWritable> output, Reporter reporter) throws IOException {

LOGGER.info(String.format("Recibimos el par <%s,%s>", numLinea.toString(), txtLinea));
final StringTokenizer s = new StringTokenizer(txtLinea.toString(),";");
s.nextToken();
s.nextToken();
final int numMuertos = Integer.parseInt(s.nextToken());
final String distrito = s.nextToken();

LOGGER.info(String.format("Generamos el par <%s,%d>", distrito, numMuertos));
output.collect(new Text(distrito), new IntWritable(numMuertos));
}
}

Clase Reducer. Donde se implementa la rutina de reduccion.

package org.casa;

import java.io.IOException;
import java.util.Iterator;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.MapReduceBase;
import org.apache.hadoop.mapred.OutputCollector;
import org.apache.hadoop.mapred.Reducer;
import org.apache.hadoop.mapred.Reporter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AccidentesPorDistritoReducer extends MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable> {

private static final Logger LOGGER = LoggerFactory.getLogger(AccidentesPorDistritoReducer.class);

@Override
public void reduce(Text distrito, Iterator<IntWritable> muertosDistrito, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {

LOGGER.info(String.format("Recibimos el par <%s,[VALORES]>", distrito.toString()));
final Text key = distrito;
int totalMuertosDistrito = 0;
int parcialMuertosDistrito = 0;
while (muertosDistrito.hasNext()) {
parcialMuertosDistrito = ((IntWritable)muertosDistrito.next()).get();
totalMuertosDistrito += parcialMuertosDistrito;
}
LOGGER.info(String.format("Generamos el par <%s,%d>", key, totalMuertosDistrito));
output.collect(key, new IntWritable(totalMuertosDistrito));
}
}

6.3) Ejecutamos el caso de uso


  • Nos posicionamos en el directorio raiz del proyecto, donde está el fichero pom.xml
  • Lanzamos el comando maven que genera el jar: mvn clean package
  • Lanzamos el comando Hadoop para que se elecute

    $ hadoop jar TestMapReduce-1.0.jar org.casa.AccidentesPorDistritoDriver bicicletas/accidentes-bicicletas-madrid-2017.csv salida

  • Obtendremos una consola como:

2018-04-14 13:19:17,464 INFO casa.AccidentesPorDistritoDriver: Se crea el trabajo y su configuración, asignándole un nombre
2018-04-14 13:19:17,689 INFO casa.AccidentesPorDistritoDriver: Se definen los tipos de los pares <clave,valor> de salida <texto, entero>
2018-04-14 13:19:17,691 INFO casa.AccidentesPorDistritoDriver: Se define las clases mapper y reducer
2018-04-14 13:19:17,694 INFO casa.AccidentesPorDistritoDriver: Se definen los tipos de los ficheros de entrada y salida (en ambos casos ficheros de texto plano)
2018-04-14 13:19:17,697 INFO casa.AccidentesPorDistritoDriver: Se definen los directorios de entrada/salida
2018-04-14 13:19:18,098 INFO casa.AccidentesPorDistritoDriver: Cargamos el trabajo en el cliente Hadoop
2018-04-14 13:19:18,098 INFO casa.AccidentesPorDistritoDriver: Ejecutamos el trabajo
....
....
2018-04-14 13:19:19,103 INFO casa.AccidentesPorDistritoMapper: Recibimos el par <0,01/01/2017;DE 6:00 A 6:59;1;ARGANZUELA;CALLE DE TOLEDO;120;CHOQUE CON OBJETO FIJO;BICICLETA>
2018-04-14 13:19:19,103 INFO casa.AccidentesPorDistritoMapper: Generamos el par <ARGANZUELA,1>
2018-04-14 13:19:19,104 INFO casa.AccidentesPorDistritoMapper: Recibimos el par <93,02/01/2017;DE 21:00 A 21:59;1;SAN BLAS;CALLE DE MEQUINENZA;14;CAÍDA BICICLETA;BICICLETA>
2018-04-14 13:19:19,104 INFO casa.AccidentesPorDistritoMapper: Generamos el par <SAN BLAS,1>
2018-04-14 13:19:19,104 INFO casa.AccidentesPorDistritoMapper: Recibimos el par <183,03/01/2017;DE 19:00 A 19:59;1;CENTRO;CALLE DE LA ESCALINATA;8;CAÍDA BICICLETA;BICICLETA>
2018-04-14 13:19:19,104 INFO casa.AccidentesPorDistritoMapper: Generamos el par <CENTRO,1>
2018-04-14 13:19:19,104 INFO casa.AccidentesPorDistritoMapper: Recibimos el par <273,04/01/2017;DE 21:00 A 21:59;1;CENTRO;CALLE DE LA CAVA DE SAN MIGUEL;13;CAÍDA BICICLETA;BICICLETA>
2018-04-14 13:19:19,104 INFO casa.AccidentesPorDistritoMapper: Generamos el par <CENTRO,1>
....
....
2018-04-14 13:19:19,685 INFO casa.AccidentesPorDistritoReducer: Recibimos el par <ARGANZUELA,[VALORES]>
2018-04-14 13:19:19,686 INFO casa.AccidentesPorDistritoReducer: Generamos el par <ARGANZUELA,[VALORES]>
2018-04-14 13:19:19,687 INFO casa.AccidentesPorDistritoReducer: Recibimos el par <BARAJAS,[VALORES]>
2018-04-14 13:19:19,688 INFO casa.AccidentesPorDistritoReducer: Generamos el par <BARAJAS,[VALORES]>
2018-04-14 13:19:19,688 INFO casa.AccidentesPorDistritoReducer: Recibimos el par <CARABANCHEL,[VALORES]>
2018-04-14 13:19:19,688 INFO casa.AccidentesPorDistritoReducer: Generamos el par <CARABANCHEL,[VALORES]>
2018-04-14 13:19:19,688 INFO casa.AccidentesPorDistritoReducer: Recibimos el par <CENTRO,[VALORES]>
2018-04-14 13:19:19,690 INFO casa.AccidentesPorDistritoReducer: Generamos el par <CENTRO,[VALORES]>
....
....
2018-04-14 13:19:19,946 INFO mapreduce.Job: Job job_local296713609_0001 completed successfully
2018-04-14 13:19:19,953 INFO mapreduce.Job: Counters: 30
....
....

  • Obtendremos, el la carpeta salida, un fichero 'part-00000' con un contenido ordenado como el siguiente:

ARGANZUELA 60
BARAJAS 18
CARABANCHEL 44
CENTRO 149
CHAMARTIN 24
CHAMBERI 56
...

REFERENCIAS:

https://www.guru99.com/create-your-first-hadoop-program.html
https://datos.madrid.es/portal/site/egob/

lunes, 19 de marzo de 2018

Pruebas con ActiveMQ

Instalación de ActiveMQ

1) Descargamos el software 'apache-activemq-5.15.3-bin.tar.gz' desde http://activemq.apache.org/download.html
2) Descomprimimos el fichero tar zxvf apache-activemq-5.15.3-bin.tar.gz en $HOME
3) Levantamos el servidor. El script a lanzar será uno de los siguientes dependiendo de nuestro sistema: /bin/linux-x86-64/activemq o /bin/linux-x86-32/activemq):

   $ cd $HOME
   $ ./apache-activemq-5.15.3/bin/linux-x86-64/activemq start
   Starting ActiveMQ Broker...

4) Paramos el servidor:

   $ cd $HOME
   $ ./apache-activemq-5.15.3/bin/linux-x86-64/activemq stop
   Stopping ActiveMQ Broker...
   Stopped ActiveMQ Broker.

5) Consolta de gestión http://127.0.0.1:8161/admin/ (admin/admin)

Introducción a JMS

Un canal de intercambio de mensajes es un lugar común donde ciertas aplicaciones publican mensajes que son consumidos por otras aplicaciones.

En un sistema JMS participan 4 componentes:
  • publicador. Aplicación que genera mensajes y los publica en el canal.
  • consumidor. Aplicación que consume los mensajes de un canal y los procesa
  • mensaje. Información que se pasan entre publicador y consumidor, ambos conocen el formato del mensaje.
  • canal. Lugar por donde se intercambian los mensajes
JMS funciona de forma asíncrona, el publicador y el consumidor no necesitan estar conectados a la vez, ya que el servidor que alberga los canales actúa de intermediario, guardando y distribuyendo los mensajes a medida que el consumidor pueda atenderlos.

En JMS hay dos tipos de canales: Queue (colas) y Topic
  • Queue (cola). Uno o varios publicadores generan mensajes para uno o varios consumidores y cada mensaje le llega a un único consumidor. Los publicadores van dejando sus mensajes en la cola. Los mensajes son recuperados en orden FIFO por el consumidor que solicita un mensaje. Si no hay ningún consumidor solicitando mensajes, la cola los guarda. Si un mensaje es procesado por un consumidor no le llega a los demás consumidores.
  • Topic. Uno o varios publicadores generan mensajes para uno o varios consumidores y cada mensaje le llega a todos los consumidores 'online'. Los publicadores van dejando sus mensajes en el 'topic', los mensajes que llegan al 'topic' se reenvían a todos los suscriptores online (cada suscriptor levanta un listener). Todos los mensajes publicados en el topic mientras un suscriptor permanezca offline se perderán para ese suscriptor.
Ejemplo Cola JMS

package activemq;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Productor implements Runnable {

private static final Logger LOGGER = LoggerFactory.getLogger(Productor.class);

private ConnectionFactory connectionFactory;
private Connection connection;
private Session session;
private Queue queue;
private MessageProducer messageProducer;
private String clientId;

public void run() {
try {
sendMensaje();
closeConnection();
} catch (JMSException e) {
e.printStackTrace();
}
}

public Productor(String clientId, String queueName) throws JMSException {

// Creamos la factoria de conexiones
this.connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);

// Creamos una conexión
this.clientId = clientId;
this.connection = connectionFactory.createConnection();
this.connection.setClientID(this.clientId);

// Creamos una sesion
this.session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

// Creamos la cola a la que enviaremos los mensajes (si la cola ya existe no se crearía)
this.queue = session.createQueue(queueName);

// Creamos el productor de mensajes
messageProducer = session.createProducer(this.queue);
}

private void closeConnection() throws JMSException {

// Cerramos la conexion
connection.close();
}

private void sendMensaje() throws JMSException {

// Creamos un mensaje JMS de tipo TextMessage
final String text = "Productor ["+clientId+"] genera el mensaje ["+ System.currentTimeMillis()+"]";
final TextMessage textMessage = session.createTextMessage(text);

// Enviamos el mensaje a la cola definida
messageProducer.send(textMessage);

LOGGER.info(text);
}
}

package activemq;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Consumidor implements Runnable {

private static final Logger LOGGER = LoggerFactory.getLogger(Consumidor.class);

private ConnectionFactory connectionFactory;
private Connection connection;
private Session session;
private Queue queue;
private MessageConsumer messageConsumer;
private String clientId;

public void run() {
try {
getTexto(250);
closeConnection();
} catch (JMSException e) {
e.printStackTrace();
}
}

public Consumidor(String clientId, String queueName) throws JMSException {

// Creamos la factoria de conexiones
this.connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);

//  Creamos una conexión
this.clientId = clientId;
connection = connectionFactory.createConnection();
connection.setClientID(clientId);

// Creamos una sesion
this.session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);

// Creamos la cola de la que recibiremos los mensajes (si la cola ya existe no se crearía)
this.queue = session.createQueue(queueName);

// Creamos el consumidor de mensajes
messageConsumer = session.createConsumer(queue);

// Iniciamos la conexión para poder recibir mensajes
connection.start();
}

private void closeConnection() throws JMSException {

// Cerramos la conexion
connection.close();
}

private void getTexto(int timeout) throws JMSException {

// Solicitamos un mensaje de la cola y esperamos timeout milisegundos la llegada de uno si no hubiese nada en la cola
Message message = messageConsumer.receive(timeout);

String texto = null;

// Verificamos si se ha recibido un mensaje
if (message != null) {

// Casteamos el mensaje al tipo previamente acordado (en este caso un TextMessage)
TextMessage textMessage = (TextMessage) message;

// Recuperamos el texto del TextMessage
texto = textMessage.getText();

// Confirmamos al broker (a nuestro ActiveMQ) que se ha procesado correctamente el mensaje
message.acknowledge();

}
LOGGER.info("Consumidor ["+clientId+"] procesa el mensaje ["+ texto+"]");
}

}

package activemq;

import javax.jms.JMSException;

public class TestActiveMQ {

public static void main(String[] args) throws JMSException, InterruptedException {
testColas();
}
public static void testColas() throws JMSException, InterruptedException {

//Levantamos 5 productores de mensajes en threads independientes cada uno envía su mensaje a la cola
Thread tp;
for (int i=0;i<5;i++) {
tp = new Thread(new Productor("p"+i,"miCola"), "tp"+i);
tp.start();
}

//Levantamos 8 consumidores de mensajes en threads independientes, cada uno procesa un mensaje de la cola
//observaremos en el log que los tres últimos consumidores no obtienen nada de la cola.
Thread tc;
for (int i=0;i<8;i++) {
tc = new Thread(new Consumidor("c"+i,"miCola"), "tc"+i);
tc.start();
}
}
}

Ejemplo Topic JMS

package activemq;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Publicador implements Runnable {

private static final Logger LOGGER = LoggerFactory.getLogger(Publicador.class);

private ConnectionFactory connectionFactory;
private Connection connection;
private Session session;
private Topic topic;
private MessageProducer messageProducer;
private String clientId;

public void run() {
try {
sendMensaje();
closeConnection();
} catch (JMSException e) {
e.printStackTrace();
}
}

public Publicador(String clientId, String topicName) throws JMSException {

// Creamos la factoria de conexiones
this.connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);

// Creamos una conexión
this.clientId = clientId;
this.connection = connectionFactory.createConnection();
this.connection.setClientID(this.clientId);

// Creamos una sesion
this.session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

// Creamos el topic a la que enviaremos los mensajes (si el topic ya existe no se crearía)
this.topic = session.createTopic(topicName);

// Creamos el productor de mensajes
messageProducer = session.createProducer(this.topic);
}

private void closeConnection() throws JMSException {

// Cerramos la conexion
connection.close();
}

private void sendMensaje() throws JMSException {

// Creamos un mensaje JMS de tipo TextMessage
final String text = "Productor ["+clientId+"] genera el mensaje ["+ System.currentTimeMillis()+"]";
final TextMessage textMessage = session.createTextMessage(text);

// Enviamos el mensaje al topic definido
messageProducer.send(textMessage);

LOGGER.info(text);
}
}

package activemq;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Suscriptor implements Runnable, MessageListener {

private static final Logger LOGGER = LoggerFactory.getLogger(Suscriptor.class);

private ConnectionFactory connectionFactory;
private Connection connection;
private Session session;
private Topic topic;
private MessageConsumer messageConsumer;
private String suscriptorId;
private String topicName;

public void run() {
LOGGER.info("Suscriptor ["+this.suscriptorId+"] iniciando la escucha del topic ["+this.topicName+"]...");
}

public Suscriptor(String suscriptorId, String topicName) throws JMSException {

// Creamos la factoria de conexiones
this.connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);

//  Creamos una conexión
this.suscriptorId = suscriptorId;
connection = connectionFactory.createConnection();
connection.setClientID(this.suscriptorId);

// Creamos una sesion
this.session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);

// Creamos el topic del que recibiremos los mensajes (si el topic ya existe no se crearía)
this.topicName = topicName;
this.topic = session.createTopic(this.topicName);

// Creamos el consumidor de mensajes
this.messageConsumer = session.createConsumer(this.topic);
this.messageConsumer.setMessageListener(this);
this.connection.start();
}

// Método que será invocado cuando llegue un mensaje
public void onMessage(Message message) {

String texto = null;

// Casteamos el mensaje al tipo previamente acordado (en este caso un TextMessage)
TextMessage textMessage = (TextMessage) message;

// Recuperamos el texto del TextMessage
try {
texto = textMessage.getText();
LOGGER.info("Suscriptor ["+this.suscriptorId+"] procesa el mensaje ["+ texto+"]");
} catch (JMSException e) {
e.printStackTrace();
}
}
}

package activemq;

import javax.jms.JMSException;

public class TestActiveMQ {

public static void main(String[] args) throws JMSException, InterruptedException {
testTopic();
}

public static void testTopic() throws JMSException, InterruptedException {

//Levantamos 3 consumidores de mensajes en threads independientes, cada uno procesa todo
//lo que llegue al topic. Conectamos antes los suscriptores dado que en caso de levantar
//primero los publicadores no llegaría nada a los suscriptores y además la cola se vería
//vacía dado que no había nadie escuchando
Thread tc;
for (int i=0;i<3;i++) {
tc = new Thread(new Suscriptor("sus"+i,"miTopic"), "tsus"+i);
tc.start();
}

//Levantamos 2 productores de mensajes en threads independientes cada uno envía su mensaje al topic
Thread tp;
for (int i=0;i<2;i++) {
tp = new Thread(new Publicador("pub"+i,"miTopic"), "tpub"+i);
tp.start();
}
}
}

Referencias:
https://www.codenotfound.com/jms-point-to-point-messaging-example-activemq-maven.html
https://www.codenotfound.com/jms-publish-subscribe-messaging-example-activemq-maven.html


miércoles, 21 de febrero de 2018

Resumen Typescript

Hola

En esta entrada dejo un repaso bastante completo al lenguaje de programación Typescript.

Todos los ejemplos de este resumen están basados en la documentación disponible en:

     https://www.typescriptlang.org/docs/handbook/basic-types.html
     https://www.tutorialspoint.com/typescript/index.htm

Para ejecutar cualquiera de los ejemplos deberíamos lanzar:

     tsc 001-tipos-basicos.ts     // Compila a javascript
     node 001-tipos-basicos.js    // Ejecuta el código en consola

La lista completa de ejemplos es:

001-tipos-basicos.ts
002-constantes-variables.ts
003-String.ts
004-Arrays.ts
005-estructuras.ts
006-operadores.ts
007-interfaces.ts
008-clases.ts
009-clasesDos.ts
010-funciones.ts
011-recursividad.ts
012-callbacks.ts
013-secuencial.ts
014-promesas.ts
015-generics.ts
016-generics2.ts

El enlace al zip con todos los ejemplos es el siguiente:

     https://mega.nz/#!lZomnICb!XvUZiEsC1BBlkt517LHQU4Gp4A5x9sZWC-3D0t5WuUQ

viernes, 9 de febrero de 2018

Introducción a blockchain y futuros problemas

Conceptos previos

Antes de empezar conviene recordar que blockchain se basa en un sistema criptográfico llamado RSA y en el concepto de huella electrónica.

Sistema RSA

En el sistema criptográfico RSA, cada participante es propietario de dos claves, una pública (disponible para cualquier otro usuario del sistema) y otra privada (exclusiva del usuario y que debe ser protegida).

Ambas claves están asociadas de forma que la operación que se realiza con una de las claves se puede deshacer con la otra clave.

En el sistema RSA cualquier usuario A puede firmar electrónicamente un documento (normalmente se firma una huella del documento y no el documento completo) con su clave privada y enviarlo a un usuario B. Como el usuario A firma con su clave privada, únicamente conocida por A, la firma garantiza que fué el usuario A quien firmó el documento. El usuario B, tras recibir el documento, puede disponer de la clave pública del usuario A (el firmante) para verificar que lo firmó A y que el documento no ha sido adulterado tras la firma.

En el sistema RSA cualquier usuario A puede enviar un documento encriptado a un usuario B, para encriptar el documento el usuario A obtiene la clave pública del usuario B y con dicha clave cifra el documento y lo envía. El documento cifrado sólo puede ser descifrado con la clave privada que posee el usuario B, por lo que aun siendo interceptado el documento por un tercer usuario, éste no podría leer el documento.

Al par de certificados que posee cada usuario se le llama certificado

Los certificados son emitidos por ciertas entidades (autoridades certificadoras) en las que confían implícitamente todos los participantes del sistema.

Huellas electrónicas

Existen procedimiento computacionales capaces de generar un extracto único de cualquier fichero. Dicho extracto se llama huella electrónica y tiene unas características especiales:

  • No es posible obtener el documento original a partir de la huella
  • No es posible encontrar un documento distinto que genere la misma huella (sería posible, pero serían millones de años de pruebas)
  • La huella tiene siempre el mismo tamaño (por ejemplo 256 bits) independientemente del tamaño del documento original
  • Si un documento resulta cambiado aunque sea una coma, la nueva huella electrónica es completamente distinta de la anterior
  • La generación de la huella es muy muy rápida

Conceptos generales de blockchain

Es una base de datos distribuida donde se almacena una CADENA de BLOQUES.

La cadena de bloques está REPLICADA en cada uno de los servidores de la red, a los que se llama MINEROS. Hay miles de servidores en la red de bloques, por lo que hay MILES DE COPIAS de la CADENA.

Cada bloque almacena las N transacciones que se han realizado en un cierto intervalo de tiempo.

La cadena de bloques sigue una secuencia temporal, cada bloque apunta al bloque anterior (a la huella HASH su bloque antecesor), por lo que siguiendo la secuencia de bloques tenemos la secuencia de transacciones de todos los usuarios del sistema desde que éste arrancó en el 2009. Es por tanto posible recorrer la cadena de bloques y recopilar el 'historial' de transacciones que un usuario del sistema ha realizado.

Si un nodo de la red (MINERO) fallase, no se pierde la información dado que hay miles de nodos adicionales.

Ninguno de los BLOQUES de la CADENA puede ser alterado dado que eso cambiaría su huella y rompería la cadena al quedar DESCONECTADO de su bloque sucesor. Esta alteración de un bloque en uno de los N servidores (MINEROS) se detecta inmediatamente al existir MILES de copias de la cadena.

Cada uno de los BLOQUES almacena N transacciones, el tamaño del bloque viene determinado por el número de usuarios y por la capacidad de cómputo necesaria para que los MINEROS generen la HUELLA del bloque. El formato de los bloques es el siguiente:

  CABECERA -> Tiene la huella hash del bloque anterior
  DATOS    -> Con un total de N transacciones
    TRANSACCION de un usuario
    TRANSACCION de un usuario
    TRANSACCION de un usuario
    ...
    TRANSACCION de un usuario
    TRANSACCION final del minero que cierra el bloque
  HUELLA   -> Huella SHA del presente bloque

Cada una de las transacciones almacenadas en un BLOQUE de la cadena tiene los siguientes datos:

  Clave pública del usuario A.
  Clave pública del usuario B.
  Dato transferido que podría estar cifrado para que únicamente B lo lea.
  Huella del dato transferido.
  Firma electrónica del dato por parte de A.
  Firma electrónica del dato por parte de B.

Participantes en blockchain

En blockchain únicamente hay tras roles:

  Usuarios que registran transacciones por pares
  Mineros que almacenan las transacciones
  Entidades certificadores que generan el par de claves (certificado) que identifica a cada usuario y cada minero

Funcionamiento de blockchain

El sistema tiene en líneas generales el siguiente funcionamiento:

Un usuario A quiere realizar una transaccion con un usuario B, con lo que envía su solicitud de transacción al MINERO de la red BLOCKCHAIN mas cercano. Dicha solicitud lleva:

  Clave pública del usuario A.
  Clave pública del usuario B.
  Dato transferido que podría estar cifrado para que únicamente B lo lea.
  Huella del dato transferido.
  Firma electrónica del dato por parte de A.

El MINERO de A almacena la transaccion en el bloque actual (el que todos los nodos de la red están rellenando con las transacciones de los últimos N minutos) y transmite la transaccion a los restantes MINEROS de la red (para que la almacenen en su copia del nodo actual) y uno de ellos la entrega al usuario B, quien la firma electrónicamente con lo que la transaccion ya yiene:

  Clave pública del usuario A.
  Clave pública del usuario B.
  Dato transferido que podría estar cifrado para que únicamente B lo lea.
  Huella del dato transferido.
  Firma electrónica del dato por parte de A.
  Firma electrónica del dato por parte de B.

La transaccion completa es entregada el MINERO de B que la propaga al restante grupo de mineros.

Cuando el bloque actual está casi completo (sólo cabe una transaccion mas), todos los mineros compiten por generar la HUELLA SHA256 de ese último bloque. Esta huella no se genera de la forma habitual (que sería un proceso casi inmediato) sino que tiene que cumplir cierta restricción que precisa mucho tiempo de computación. La restricción es que los N primeros bits de la huella SHA256 deben ser ceros.

Los MINEROS compiten por generar esa huella y quien la genera cierra el bloque (que aloja como última transacción que fué el minero X quien cerró el bloque) y propaga el bloque cerrado al resto de MINEROS para que arranque un nuevo bloque en la cadena.

Por cada bloque cerrado un MINERO recibe una importante cantidad de DINERO. Dinero que invierte en instalaciones y equipos informáticos que le permiten COMPETIR mejor con los restantes mineros para tener capacidad de cómputo suficiente como para cerrar un % mayor de los bloques.

Se cierra un bloque cada 10 minutos, por lo que todos los mineros tienen un fuerte incentivo económico.

Aplicaciones de blockchain

El sistema blockchain tiene potencial para cambiar muchos de los sistemas que operan hoy en día en el mundo dado que elimina intermediarios y registros privados:

  • Mis datos bancarios son una secuencia de operaciones de dos tipos, operaciones de cobro (nómina) y de pago (mis gastos), por tanto no necesito que únicamente sean los ordenadores de mi banco (unos cuantos MINEROS de su red privada) quienes guarden esa información y/o el dinero físico, puedo confiar en una criptomoneda y en la red de miles de MINEROS de blockchain. Evito así problemas de quiebras bancarias o problemas de corralitos en países inestables.
  • Las transferencias de dinero que realizan los emigrantes a sus países de destino pueden prescindir de entidades intermediarias si se realizan en criptomonedas basadas en blockchain
  • Los contratos de compraventa de propiedades (coches, terrenos, casas, etc) pueden realizarse entre pares prescindiendo de notarios y de un registro centralizado de la propiedad
  • El historial sanitario de una persona no tiene porqué estar almacenado en N hospitales, pertenece a la persona que lo almacena en blockchain
  • El registro de la propiedad intelectual (músicos, escritores, patentes, etc) no tiene que estar gestionado por N registros nacionales sino que puede ser universal y quedar almacenado en blockchain

Futuros problemas del blockchain

Tras este resumen quedan aún muchos detalles pendientes de aclarar:
  • ¿Todas las transacciones son por pares?
  • ¿Qué ocurre si al cabo de los años una CA grande que generó miles de certificados desaparece?
  • ¿Qué pasaría si un gigante tipo Google con millones de nodos entrase como minero?
  • ¿Cuanta energía se consume en labores de minería?
  • Si se usa blockchain de forma masiva ¿cuantas transacciones pueden registrarse por segundo?
  • Si se usa blockchain de forma masiva ¿será manejable el tamaño de la lista de bloques?
  • Si se quiere confidencialidad se deben cifrar los datos de la transacción antes de añadirla a un bloque, pero ¿cómo garantizamos que esos datos sigan estando protegidos dentro de 25 años cuando la técnica deje obsoletos los mecanismos de cifrado actuales?
  • Si en un futuro son los gobiernos quienes impulsan las criptomonedas y registran el 100% de nuestras transacciones, ¿donde queda la privacidad? y ¿quien garantiza que esos datos no terminan llegando a manos privadas?

Referencias
  • https://es.wikipedia.org/wiki/Cadena_de_bloques
  • https://www.youtube.com/watch?v=hEoYL5j0wYU
  • https://www.youtube.com/watch?v=9INjZuOVLfg
  • https://www.youtube.com/watch?v=ayRkvlC31y8
  • https://www.youtube.com/watch?v=44D9nVxqGIE

sábado, 3 de febrero de 2018

Ejecutar synaptic desde un lanzador Gnome en Ubuntu 17.10

Hola

Tras instalar Ubuntu (en este caso la versión 17.10) he visto que tras instalar synaptic (sudo apt-get install synaptic) no conseguía ejecutarlo ni desde el lanzador Gnome ni desde la línea de comandos con el tradicional (sudo synaptic).

Googleando un poco parece ser porque han limitado los permisos del root para lanzar aplicaciones gráficas. Para solventarlo he movido el lanzador predefinido tras la instalación desde la carpeta por defecto (donde aplica todos los usuarios del sistema) a mi carpeta $HOME:

sudo mv synaptic.desktop /home/egdepedro/.local/share/applications/

Seguidamente he cambiado los permisos del fichero:

sudo chown egdepedro:egdepedro synaptic.desktop

Por último he editado el lanzador (vi synaptic.desktop) ajustando la línea Exec con:

[Desktop Entry]
Name=Synaptic Package Manager
GenericName=Package Manager
Comment=Install, remove and upgrade software packages
Exec=bash -c 'xhost +SI:localuser:root; pkexec synaptic'
Icon=synaptic
Terminal=false
Type=Application
Categories=PackageManager;GTK;System;Settings;
X-Ubuntu-Gettext-Domain=synaptic

El comando da permisos al usuario root para lanzar aplicaciones gráficas y seguidamente lanza el synaptic.

Esto es todo

viernes, 2 de febrero de 2018

Sobreescribir vs borrar un disco duro

¿Porqué es mejor sobreescribir que formatear un disco para limpiar su contenido?

Todo disco duro que hayamos tenido en nuestro poder suele tener datos personales que comviene borrar. Si el disco va a ser utilizado por terceros conviene asegurarnos de borrar correctamente su contenido.

Hay varias formas de abordar el problema:

1) Borrar los ficheros personales

Es muy poco recomendable dado que cuando borramos un fichero de nuestro disco duro lo que realmente estamos haciendo es borrar la referencia de dicho fichero de la tabla de contenido del disco, por lo que en realidad esos 'bloques de disco' quedan disponibles para ser sobreescritos con los datos de nuevos ficheros que entrasen en el disco.

Como el borrado de la referencia en la tabla de contenido no implica el borrado real de los datos del fichero, los datos que creemos borrados permanecen en el disco mucho tiempo si no tenemos la 'suerte' de que sean sobreescritos por un nuevo fichero que se almacene en el disco.

Hay además mucho software especializado en recuperar ficheros borrados que cualquier persona maliciosa que hubiese heredado nuestro disco podría usar.

2) Formatear de forma rápida el disco

Es un método tan malo como el anterior. Un formateo 'rápido' del disco es en realidad un borrado completo de la tabla de contenido del disco, por lo que en realidad los 'bloques de disco' siguen teniendo los mismos datos que antes del formateo y siguen siendo 'recuperables' mediante un software especialidado por cualquier persona maliciosa que hubiese heredado nuestro disco.

3) Formatear de forma segura el disco

Es un método mucho mas seguro que los anteriores, en este caso además de borrar completamente de la tabla de contenido del disco, se escriben 'ceros' en toda la superficie del disco, por lo que la recuperación de los datos es ya imposible sin software/hardware muy muy especializado.

4) Sobreescribir el disco

Si el método anterior sobreescribe todo el disco con ceros, ¿cómo puede ser recuperado el anterior contenido del disco?.

La explicación es muy simple:
  • Primero debemos recordar que los datos de un disco son almacenados magnéticamente, cuando grabamos un cero en un disco estamos orientando una pequeñísima cantidad de material ferromagnético de una posición específica (plato/cabeza/cilindro/pista/sector) en modo N-S. Si grabamos un uno el mismo 'iman' queda orientado en modo S-N.
  • Si nuestro fichero tuviese el contenido '1010110', lo tendríamos almacenado en el disco con S-N,N-S,S-N,N-S,S-N,S-N,N-S. Si borramos el disco sobreescribiendo todo con ceros tendríamos '0000000' y los imanes tendrían N-S,N-S,N-S,N-S,N-S,N-S,N-S con lo que todo parece correcto y la información anterior es irrecuperable.
  • Pero todo lo anterior es correcto en un mundo digital, en un mundo analógico uno de esos mini-imanes que tenía cero (N-S) y ha pasado a tener uno (S-N) tiene una intensidad de campo magnético 'inferior' a la que tendría un iman sobre el que se hubiesen grabado muchas veces seguidas un uno (S-N). Imaginemos que en el primer caso la intensidad del campo magnético (S-N) es de un 85% y en el segundo caso de un 99%, siendo ambos valores leídos como uno por un brazo de disco normal. Pero existen lectores mas sensibles capaces de detectar esa diferencia y por tanto inferir que el uno que se lee al 85% era antes un cero mientras que el uno que se lee al 99% era antes un uno por lo que el formateo de disco con una única sobreescritura de ceros en todo el disco es 'hackeable'.
El borrado mas seguro es el que realiza varias sobreescrituras del disco con datos aleatorios (cuantas mas sobreescrituras completas mejor) y finalmente unos cuantos formateos seguros del disco donde se ponga todo a ceros. Así minimizamos las posibles fluctuaciones de intensidad en cada 'iman' del disco.

Eso es todo

miércoles, 31 de enero de 2018

Borrado 'seguro' de un disco duro mediante utilidades linux

Hola

Vamos a comentar en esta entrada como realizar un borrado 'seguro' de
un disco duro mediante utilidades linux recogidas en la distribución
Ubuntu.

Lo primero es identificar con toda seguridad el disco que queremos
borrar, no sea que borremos por error otro disco o partición. Para
ello uno de los comandos mas útiles es:

  sudo lsblk -fm

El comando saca información sobre todos los dispositivos de bloque
(lease discos) definidos en el sistema. Para mas información podemos
consultar el manual (man lsblk).

Generará una salida de tipo:

NAME   FSTYPE LABEL                     UUID                                 MOUNTPOINT                 NAME     SIZE OWNER GROUP MODE
sda                                                                                                     sda    298,1G root  disk  brw-rw----
├─sda1 swap                             58f76d10-0d5a-4c13-a26e-cf2ea0cb4466 [SWAP]                     ├─sda1  10,5G root  disk  brw-rw----
├─sda2                                                                                                  ├─sda2     1K root  disk  brw-rw----
├─sda5 ext4                             770bee2a-5c56-4a75-b0bc-7726d8e80aab /home                      ├─sda5 256,1G root  disk  brw-rw----
└─sda6 ext4                             9bf5a0df-326c-46a7-bf1f-d138b9cb4e51 /                          └─sda6  31,5G root  disk  brw-rw----
sdb                                                                                                     sdb    931,5G root  disk  brw-rw----
└─sdb1 ntfs   Seagate Backup Plus Drive 2EE49D74E49D3ED1                     /media/egdepedro/Seagate B └─sdb1 931,5G root  disk  brw-rw----
sr0                                                                                                     sr0     1024M root  cdrom brw-rw----

Las columnas de datos son:

  • NAME. Nombre del disco (sda y sdb) y debajo un arbol con el nombre de las particiones definidas (sda1, sda2, sda3, sda4 y sdb1). En la imagen anterior tendríamos 4 particiones dentro del disco sda y una partición en el disco sdb.
  • FSTYPE. Tipo de File System de cada partición.
  • LABEL. Etiqueta de cada partición modificable por los usuarios. Suele introducirse un nombre lógico como 'datos', 'sistema', etc.
  • UUID. Identificador único de cada partición.
  • MOUNTPOINT. Punto de montaje de cada partición.
  • SIZE. Tamaño de cada disco/particion en GB.
  • OWNER. Usuario propietario del disco/partición.
  • GROUP. Grupo propietario del disco/partición.
  • MODE. Opciones de son las opciones de montaje de cada disco duro o partición.

Una vez identificado el nombre 'NAME' del disco/partición a borrar
lanzaremos el comando:

  shred -f --random-source=/dev/urandom -n 10 -v -z /dev/sda

El comando sobreescribe el disco indicado tantas veces como deseemos (-n 10), con datos aleatorios (--random-source=/dev/urandom), machacando incluso areas sobre las que no tuviésemos permisos (-f), nos muestra el progreso (-v) y rellena con ceros el final de cada fichero para ocultar el triturado (-z).

Para mas información podemos consultar: http://www.gnu.org/software/coreutils/shred

Nota. Si queremos sobrescribir el disco con ceros en vez de datos aleatorios (a priori mucho menos seguro por lo que se precisan mas pasadas) podemos usar (--random-source=/dev/zero). Apuntar además que el proceso anterior (con valores aleatorios) es bastante lento.

Eso es todo