Cómo y por qué utilizar ZRAM como "espacio de intercambio" o SWAP

Advertencia

Este artículo contiene algunas erratas. Puedes ver los detalles al final del texto.

Ya he nombrado ZRAM en artículos anteriores. Hoy toca abordarlo en profundidad. Puedes empezar leyendo su página en la wikipedia. Pongo el enlace a la versión en inglés porque la versión en español es vergonzosa.

ZRAM es un servicio disponible en Linux (otros sistemas operativos tienen servicios similares) que permite utilizar parte de la memoria RAM como un disco duro virtual, más exactamente como un "dispositivo de bloques".

Nota

En los viejos tiempos esto permitía, por ejemplo, usar esa memoria RAM que no estás utilizando como un disco ram en el que copiar los datos desde uno o más discos flexibles y así trabajar con más rapidez y comodidad. En mis tiempos de Atari ST con mis cuatro megabytes de RAM, podía dedicar dos megabytes a meter el contenido de dos o tres discos de 3.5 pulgadas en memoria y trabajar a toda velocidad sin tener que esperar al disco ni, más importante, tener que sacar y meter uno u otro disco flexible todo el tiempo. Podías tener en el disco ram el editor, el compilador, el enlazador y el programa binario final, mientras el código fuente que estabas escribiendo residía en un disco flexible.

La tentación de grabar el código fuente también en el disco ram era fuerte... Tengo algunas anécdotas que contar al respecto, sobre lo buena que era la fuente de alimentación de mi Atari ST ante microcortes de energía eléctrica...

Una peculiaridad interesante de ZRAM es que le indicamos un tamaño determinado, pero los datos se almacenan de forma comprimida y, por tanto, su consumo de RAM es dinámico. Esto es interesante, porque un ZRAM sin usar apenas ocupa memoria por mucho tamaño que le hayamos dado. Por otro lado, si los datos se comprimen bien, el ZRAM ocupa menos memoria que los datos originales.

Esta característica de compresión de datos permite un uso interesante de ZRAM: utilizarlo como espacio de intercambio o swap.

La idea es la siguiente: supongamos que tenemos 1GB de memoria RAM y usamos un ZRAM de 1GB (por ejemplo) como espacio de intercambio. Mientras el sistema operativo no hace swap, el ZRAM no ocupa espacio; tenemos toda la RAM para el usuario. A medida que vamos gastado RAM y la agotamos, el sistema operativo empieza a enviar las páginas de memoria que hace más tiempo que no se usan al espacio de intercambio proporcionado por la ZRAM. Esto libera memoria, pero, simultáneamente, la ZRAM crece y ocupa memoria. A priori no parece que ganemos nada, salvo por el hecho de que los datos se comprimen, así que se libera más RAM de la que consume el ZRAM. Por ejemplo, si la compresión es 2:1 y se envían 400 megabytes al ZRAM, esos datos ocuparán 200 megabytes, y se liberarán los otros 200 megabytes de RAM, obteniendo una ganancia neta de 200 megabytes.

Es decir, si tenemos 1 GB de RAM, configuramos un ZRAM de 1 GB y la compresión media es 2:1, podemos trabajar casi como si realmente tuviésemos 2 GB de RAM.

Que esto funcione bien o mal depende solo de lo único que importa cuando hablamos de espacio de intercambio: todo irá bien mientras la cantidad de memoria RAM disponible sea lo bastante grande como para abarcar todo nuestro Working Set.

Leer más…

Actualización de características en un sistema de ficheros ZFS (20200528)

A medida que ZFS evoluciona, se van desplegando características opcionales. Para preservar la compatibilidad, estas características nuevas son opcionales, un ZPOOL ZFS que no las utilice será compatible con versiones previas de ZFS.

Activar características opcionales en un ZPOOL ZFS es una decisión que no debe tomarse a la ligera. Algunas sí, pero no todas las características opcionales pueden desactivarse una vez que se activan en un ZPOOL ZFS. Por tanto, la regla de oro es que solo hay que activar características cuando hayamos tomado la decisión irrevocable de no volver atrás en una versión determinada de ZFS.

En esta valoración hay que considerar también con atención los mecanismos de los que disponemos ante una fallo catastrófico del sistema operativo. Por ejemplo, ¿el entorno de recuperación que tenemos en el pendrive USB es capaz de importar un ZPOOL ZFS con esa característica activada?

El ciclo de vida de una característica opcional suele ser el siguiente:

  1. Deshabilitada: La implementación ZFS soporta una característica determinada, pero no está activada en un ZPOOL ZFS dado. Esto permite importar el ZPOOL ZFS en una versión anterior de ZFS, si fuera necesario.

  2. Habilitada: Hemos activado la característica opcional, pero aún no se está utilizando. Habitualmente el ZPOOL ZFS se podrá importar en una versión anterior de ZFS. Por lo general, se podrá desactivar la característica opcional y volver a dejarla como deshabilitada.

  3. Activa: La característica opcional está en uso. Como regla general, el ZPOOL ZFS no se podrá importar en versiones de ZFS que no conozcan esa característica opcional. A veces es posible importar el ZPOOL ZFS en modo solo lectura, dependiendo de los detalles de funcionamiento de esa característica.

    Algunas características opcionales pueden pasar de activas a habilitadas y, por tanto, se podrían deshabilitar o bien importar el ZPOOL ZFS en un versión de ZFS que no reconozca esa característica. Por ejemplo, una característica que afecte a la destrucción de un dataset puede pasar de habilitada a activa mientras se está destruyendo un dataset, pasando nuevamente a habilitada al terminar.

    No obstante, como regla general, una característica que pasa a estar activa permanecerá en ese estado permanentemente. Normalmente esto se documenta en cada característica.

Vayamos al grano.

Una de mis máquinas lleva muchos meses funcionando con la misma versión del sistema operativo y de ZFS, sin ningún problema. Considero entonces que no voy a volver a una versión anterior y, por tanto, activar características opcionales parece poco arriesgado y posiblemente ventajoso.

Veamos qué nos dice el sistema operativo:

root@csi:~# zfs upgrade
This system is currently running ZFS filesystem version 5.

All filesystems are formatted with the current version.
root@csi:~# zpool upgrade
This system supports ZFS pool feature flags.

All pools are formatted using feature flags.


Some supported features are not enabled on the following pools. Once a
feature is enabled the pool may become incompatible with software
that does not support the feature. See zpool-features(5) for details.

POOL  FEATURE
---------------
blue
      multi_vdev_crash_dump
      large_dnode
      sha512
      skein
      edonr
      userobj_accounting
datos
      multi_vdev_crash_dump
      large_dnode
      sha512
      skein
      edonr
      userobj_accounting

Leer más…

Cómo actualicé Mailman a la versión 2.1.33 en pkgsrc

La versión actual (mayo 2020) de Mailman para las zonas nativas SmartOS es la versión 2.1.29, pero tiene problemas de seguridad y los desarrolladores han publicado la versión 2.1.33. Mailman es un programa fácil de actualizar a mano (una vez que ya lo tienes instalado y configurado), pero viendo que en pkgsrc (la distribución de paquetes que usa SmartOS para sus zonas nativas) no se estaba actualizando el paquete de software, decidí que era la oportunidad perfecta para echar una mano.

Este artículo describe los pasos detallados que he seguido para proponer una actualización de Mailman en pkgsrc. Es mi primer intento de colaborar con esa comunidad y el flujo de trabajo que documento aquí seguramente ni es el mejor, ni el definitivo. Básicamente, estoy documentando esto para mí mismo, porque prefiero depender de mi memoria lo mínimo posible en cosas que hago de forma esporádica.

Nota

En lo que sigue, cuando indico un acceso SSH a pkgsrc, me refiero a una conexión a una zona nativa SmartOS de mis servidores con la imagen pkgsrc distribuída por ellos.

Leer más…

Notas de trabajo: Upgrading SmartOS when installed in your harddisk

Advertencia

ATENCIÓN:

Este artículo transcribe mis notas sobre mi intento de actualizar el arranque personalizado en mis instalaciones SmartOS de GRUB al nuevo Loader de Illumos. Mi idea era usar estas notas para escribir un artículo en inglés describiendo el proceso, pero las cosas no fueron bien y, además, el ecosistema ha cambiado de maneras muy interesantes.

ATENCIÓN:

El procedimiento descrito aquí es un "trabajo en curso" y no funciona correctamente.

Antecedentes:

Leer más…

Mejoras a la hora de generar un email diario a partir de un feed RSS (Slashdot)

En Generar un email diario a partir de un feed RSS (Slashdot) publico un pequeño programa en Python para enviarme un correo electrónico diario con los artículos publicados en Slashdot desde la última notificación.

Este programa me ha servido bien durante años, pero había un problema desconcertante: El programa se ejecuta por cron una vez por hora y se supone que tendría que enviarme el resumen diario entre las cuatro y cinco de la tarde. Normalmente era así, pero no siempre. A veces el email se enviaba horas más tarde.

La cosa no era grave, solo molesta, y nunca tenía tiempo para investigarla. Cuando por fin me metí en harina, tardé un buen ratillo en localizar el problema:

El programa hace una petición HTTP condicional al feed RSS de Slashdot para no machacar el servidor y no trabajar nosotros tampoco en vano si no ha habido cambios. La cuestión es que, si no hay nada nuevo que procesar, el programa termina inmediatamente. Es decir, no llega a la parte de "es hora de mandar el email". En resumen, el correo electrónico se enviaba solo cuando era más tarde de las 16:00 y, además, había entrado algún artículo nuevo en el feed RSS.

Una vez determinada la causa última, la solución es simple: procesar el feed RSS aunque no haya novedades. La cosa parecía sencilla, pero al final no lo fue tanto. No guardo el feed RSS original así que tengo que hacer chanchullos para que mi programa no considere que ha perdido datos. La lógica es innecesariamente compleja, el programa exige una refactorización urgente. Durante el proceso detecté otro problema: si en un momento dado el programa detecta que se han perdido datos, pero no ha llegado aún el momento de enviar el correo electrónico, no nos enteramos. Compliqué el código una vez más para que el estado "pérdida de datos" sea persistente y solo se limpie cuando se ha enviado el correo electrónico notificándolo.

El resultado es un programa que funciona correctamente, pero innecesariamente complejo y bastante frágil. Cuando se llega a este estado urge refactorizar o, con un programa tan corto, directamente reescribirlo por completo.

Las diferencias respecto a la versión anterior son:

slashdot-20181015-ddeb027b3f1c.diff (Código fuente)

--- slashdot-20181015.py        2019-10-12 02:28:46.020075954 +0200
+++ slashdot-ddeb027b3f1c.py    2021-02-11 20:18:38.341490217 +0100
@@ -1,7 +1,7 @@
 #!/usr/bin/env python3


-# (c) 2018 Jesús Cea Avión - jcea@jcea.es - https://www.jcea.es/
+# (c) 2018-2020 Jesús Cea Avión - jcea@jcea.es - https://www.jcea.es/
 # This code is licensed under AGPLv3.


@@ -28,6 +28,7 @@

         self._procesados = data['procesados']
         self._buckets = data['buckets']
+        self._solapamiento = data.get('solapamiento', True)
         self._procesados_nuevos = set()
         self.etag = data['etag']
         self.modified = data['modified']
@@ -41,10 +42,14 @@
             v['modified'] = self.modified
         return v

+    def notificado(self):
+        self._solapamiento = True
+        self.cambiado = True
+
     def solapamiento(self):
         if self.saved:
             return self._solapamiento
-        return not self._procesados.isdisjoint(self._procesados_nuevos)
+        return self._solapamiento and not self._procesados.isdisjoint(self._procesados_nuevos)

     def update(self, guid, ts, title, link, summary):
         self._procesados_nuevos.add(guid)
@@ -65,6 +70,10 @@

         return self.update(guid, ts, title, link, summary)

+    def feed304(self):
+        self._procesados_nuevos.update(self._procesados)
+        return self.etag, self.modified
+
     def itera_antiguos(self, horas=16):
         ts_antiguo = datetime.datetime.now()
         ts_antiguo -= datetime.timedelta(seconds=horas * 60 * 60)
@@ -99,6 +108,8 @@
         if not self.cambiado:
             return

+        self._solapamiento = self.solapamiento()
+
         procesados = self._procesados_nuevos
         if not purga:
             procesados.update(self._procesados)
@@ -106,6 +117,7 @@
         with open(path + '.NEW', 'wb') as f:
             data = {'procesados': procesados,
                     'buckets': self._buckets,
+                    'solapamiento': self._solapamiento,
                     'etag': etag, 'modified': modified,
                     }
             pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
@@ -115,7 +127,6 @@
         self.cambiado = False

         # Tras el save no deberíamos usar más este objeto
-        self._solapamiento = self.solapamiento()
         self.saved = True
         del self._procesados
         del self._procesados_nuevos
@@ -136,20 +147,21 @@
     finally:
         socket.setdefaulttimeout(timeout)

-    if feed.status == 304:
-        # print("Sin cambios")
-        return
-
-    for entry in feed.entries:
-        summary = BeautifulSoup(entry["summary"], 'html.parser')
-        summary = list(summary.children)[0]
-        entradas.new(entry['id'], entry.updated_parsed,
-                     entry['title'], entry['link'], summary)
-
-    no_solapamiento = ''
-    if not entradas.solapamiento():
-        no_solapamiento = '<font size=+2><b>HEMOS PERDIDO ENTRADAS</b></font>'
-        no_solapamiento += '<br/><br/>\n'
+    if feed.status == 200:
+        etag, modified = feed.get('etag'), feed.get('modified')
+        for entry in feed.entries:
+            summary = BeautifulSoup(entry["summary"], 'html.parser')
+            summary = list(summary.children)[0]
+            entradas.new(entry['id'], entry.updated_parsed,
+                         entry['title'], entry['link'], summary)
+    elif feed.status == 304:
+        # En el 304 no se manda ETAG ni Modified, así que coge los que comprobamos
+        # en la petición condicional.
+        etag, modified = entradas.feed304()
+    else:
+        raise RuntimeError(f'Estado: {feed.status}')
+
+    # Si es 304, procesamos lo viejo por si tiene que salir el email.

     html_desc = html_links = ''
     entradas_a_borrar = []
@@ -164,9 +176,14 @@
     # lo cierto es que queremos actualizar el etag y el modified AUNQUE
     # no haya habido cambios en el feed. Esto es algo a mejorar en el futuro.

-    entradas.save(feed.get('etag'), feed.get('modified'))
+    entradas.save(etag, modified)

     if html_desc:
+        no_solapamiento = ''
+        if not entradas.solapamiento():
+            no_solapamiento = '<font size=+2><b>HEMOS PERDIDO ENTRADAS</b></font>'
+            no_solapamiento += '<br/><br/>\n'
+
         html_links = f'<ul>\n{html_links}\n</ul>\n'
         html = f'''<html>
 <head>
@@ -196,14 +213,17 @@
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE)

+        entradas.notificado()
+
     # XXX: Lo suyo sería grabar solo si hay cambios de verdad, pero
     # lo cierto es que queremos actualizar el etag y el modified AUNQUE
     # no haya habido cambios en el feed. Esto es algo a mejorar en el futuro.
     entradas = items()
+    etag, modified = entradas.feed304()  # XXX: Para evitar "no solapamiento"
     for guid, ts in entradas_a_borrar:
         entradas.borrar(guid, ts)

-    entradas.save(feed.get('etag'), feed.get('modified'), purga=False)
+    entradas.save(etag, modified, purga=False)


 if __name__ == '__main__':

El código completo es el siguiente:

Leer más…

Acceso en modo pasivo a un servidor FTP que no conoce su propia IP pública

Recientemente he estado participando en un proyecto que emplea un servidor FTP [1] detrás de un NAT. El NAT tiene un puerto abierto redirigido hacia el servidor FTP interno, pero este está mal configurado y responde a los intentos de conexión en modo pasivo con su dirección IP interna, que no es válida en internet.

[1] El protocolo FTP tiene muchos inconvenientes y se ha quedado anticuado. Un sistema moderno debería utilizar un protocolo más acorde con los tiempos, como WebDAV, preferiblemente con cifrado de datos.

No quiero entrar en detalles finos sobre cómo funciona FTP, pero lo que nos interesa es que a la hora de transferir datos se puede usar un modo activo y un modo pasivo. En el modo activo, el servidor FTP intentaría conectarse a nuestra máquina, algo que hoy en día no va a funcionar fuera de una red local debido a los NAT y cortafuegos. En el modo pasivo, solicitamos al servidor de FTP detalles de conexión para conectarnos nosotros a él. El servidor FTP responderá con una dirección IP y un puerto al que conectarse.

El problema, en este caso concreto, es que la dirección IP que nos da el servidor FTP es privada y solo es válida dentro de su red local.

Maravillado ante el hecho de que el resto de compañeros no tengan problemas con esto, averiguo que todos utilizan un cliente FTP llamado FileZilla. Este cliente hace magia: si el servidor FTP responde a una petición de conexión en modo pasivo con una dirección IP privada, FileZilla la ignorará e intentará conectar a la dirección IP pública del servidor FTP. Es decir, a la dirección IP del cortafuegos o NAT en la frontera de su red.

Si dicho cortafuegos o NAT tiene mapeado el puerto hacia en servidor FTP interno, el proceso funcionará.

¿Cómo cambiar mis programas en Python para implementar algo similar?

Leer más…

Cálculo de pérdida de datos en LizardFS

Advertencia

La instalación LizardFS que describo de pasada en este documento es un compendio de malas prácticas de libro. Por favor, no utilices nada que se parezca a esta topología en tu instalación LizardFS.

Como consecuencia evidente, no interpretes este documento como que LizardFS es un mal sistema de ficheros proclive a la pérdida de datos. LizardFS tiene muchos problemas técnicos y culturales, pero este artículo no trata sobre ellos.

Personalmente, he intentado mejorar la configuración de esa instalación LizardFS, pero la resistencia de su propietario ha sido inamovible... incluso tras esta pérdida de datos.

En Detección y diagnóstico de pérdida de datos en LizardFS describo un caso catastrófico de pérdida de datos en una instalación LizardFS debido a que se configuró para que los diferentes discos duros presentes en una máquina se mostrasen a LizardFS como nodos de almacenamiento separados (y, por tanto, con fallos no correlados). Esto es conveniente para ver muchos discos duros y poder emplear una corrección de errores 8+2 tal y como describo en el artículo anterior. Lamentablemente, si se pierde un ordenador, perderemos varios discos duros a la vez. Dado que una configuración 8+2 solo corrige dos errores, es interesante calcular la probabilidad de fallo si se cae un servidor. Es decir, calcular cuántos ficheros se pierden si se cae un ordenador con cinco discos duros en una red con treinta discos duros.

Advertencia

Esto es una mala práctica. Todos los discos duros de un servidor deberían verse como un único nodo de almacenamiento. La previsión era hacerlo así en el futuro, pero, mientras la red de almacenamiento crecía, se quería utilizar redundancia 8+2. La propuesta inicial era crecer rápido en discos duros y ordenadores, y que, mientras tanto, el modelo de fallo ante el que protegerse era la pérdida de discos duros aislados. En caso de la caída momentanea de un servidor, se aceptaba que los datos no estuviesen disponibles durante unos minutos/horas.

Lamentablemente, la red de almacenamiento nunca creció, nunca se reconfiguró para ajustarse a esa nueva realidad y cuando cayó una máquina, el operador borró todos sus discos duros antes de poder recuperar los datos de la red de almacenamiento.

Un desastre, vaya.

Posiblemente se pueda dar una respuesta analítica, pero prefiero hacer el cálculo mediante simulación para tener flexibilidad a la hora de modelar la red real.

El código Python es el siguiente:

Leer más…

WeeWX y cómo cambiar el intervalo de archivo de la estación meteorológica

Por defecto, la estación meteorológica de mi padre archiva los datos cada media hora. Esto puede ser conveniente para conservar capacidad de almacenamiento en la estación base, pero al utilizar WeeWX y no tener límites de tamaño, prefiero que la grabación sea más frecuente. Por ejemplo, cada cinco minutos.

WeeWX se queja en los logs:

Jan  5 23:14:04 MeteoPI weewx[16814]: wxengine: The archive interval in the configuration file (300) does not match the station hardware interval (1800).
Jan  5 23:14:05 MeteoPI weewx[16814]: wxengine: Using archive interval of 1800 seconds
Jan  5 23:14:05 MeteoPI weewx[16814]: wxengine: Using archive database: archive_sqlite

En las versiones antiguas de WeeWX hacíamos:

$ ./wee_config_fousb --set-interval 5

En las versiones más modernas de WeeWX el proceso es:

$ bin/wee_device --info  # Obtenemos información del dispositivo
$ bin/wee_device --set-interval=5

Este cambio de configuración es persistente mientras la estación base tenga batería. Si se va la luz y se le acaba la batería de respaldo, o bien si reiniciamos la estación base a los valores de fábrica, tendremos que repetir el proceso.

Detección y diagnóstico de pérdida de datos en LizardFS

Advertencia

La instalación LizardFS que describo de pasada en este documento es un compendio de malas prácticas de libro. Por favor, no utilices nada que se parezca a esta topología en tu instalación LizardFS.

Como consecuencia evidente, no interpretes este documento como que LizardFS es un mal sistema de ficheros proclive a la pérdida de datos. LizardFS tiene muchos problemas técnicos y culturales, pero este artículo no trata sobre ellos.

Personalmente, he intentado mejorar la configuración de esa instalación LizardFS, pero la resistencia de su propietario ha sido inamovible... incluso tras esta pérdida de datos.

Hace unas semanas una instalación LizardFS que ayudo a administrar perdió un servidor completo con cinco discos duros. La red LizardFS constaba de treinta discos duros repartidos en media docena de máquinas. Los ficheros se almacenaban de forma redundante siguiendo dos políticas diferentes:

  1. Espejo: Cada fichero se almacena en dos o tres discos duros diferentes. En esta instalación LizardFS, lamentablemente, no hay reglas que prohíban que todas las copias residan en un mismo servidor (en diferentes discos duros conectados a un mismo servidor).

    Es decir, podríamos tener la mala suerte de que todas las copias residan en el mismo servidor y si perdemos el servidor entero (fallo hardware, reinicio, etc), pues tendremos datos inaccesibles porque nos desaparecen varios discos duros a la vez.

    La solucion evidente es configurar LizardFS para que considere todos los discos duros de un mismo servidor como pertenecientes a una misma zona de fallo y evite reutilizar una misma zona de fallo para almacenar las diferentes copias de redundancia.

  2. Corrección de errores 8+2: El modo espejo es simple de entender y operar, y resulta también ventajoso en rendimiento a la hora de leer datos. Su problema más evidente es el consumo de recursos: los datos ocupan el doble o el triple de lo necesario.

    Para la mayor parte de los datos almacenados en esta instalación LizardFS utilizo un esquema de redundancia 8+2. Esto significa lo siguiente:

    • Cada fichero almacenado se divide en ocho fragmentos [1].

    • Utilizando técnicas de detección y corrección de errores, se procesan esos ocho fragmentos y se generan dos fragmentos adicionales sintéticos [1].

    • Esos diez fragmentos en total (los ocho originales más los dos fragmentos de redundancia) se almacenan en diferentes discos duros [1].

      Esto implica que un fichero de 8 megabytes ocupa realmente 10 megabytes en LizardFS. Esto supone una sobrecarga de espacio del 25% en vez del 100% de sobrecarga que requiere un espejo. Además, el espejo proporciona menos resistencia, porque podemos perder información si nos desaparecen dos discos duros concretos, mientras que con ec82 podemos perder dos discos duros cualesquiera sin que nos cause problemas.

    • Cuando leemos los datos, podemos recuperar la información original si somos capaces de leer ocho fragmentos cualesquiera de los diez fragmentos almacenados (ocho de datos y dos de redundancia).

      Es decir, podemos perder dos discos duros cualesquiera sin perder datos.

      Lamentablemente, no se ha usado información topológica en esta instalación LizardFS. Aunque cada fragmento se almacena en un disco duro diferente, no hay nada que impida que haya tres o más fragmentos en discos duros conectados a un mismo servidor. Si perdemos un servidor, podríamos perder más de dos fragmentos [1] dejando el fichero inaccesible.

Leer más…

Audio de la entrevista radiofónica "Edward Snowden, ética frente a vigilancia masiva"

El 22 de noviembre de 2019 me entrevistaron en el programa de radio Olvida tu equipaje de Radio Utopía para hablar de Edward Snowden y su libro Vigilancia permanente.

Se trata de una autobiografía que describe con detalle su juventud vinculada a la informática e Internet, su trabajo posterior en la comunidad de inteligencia, sus dudas crecientes sobre la ética en juego y su determinación final de sacar los abusos ilegales a la luz, aun con las consecuencias que sabría que le acarrearían.

El libro vale mucho la pena y, además, es divertido y fácil de leer y entender.

La verdad es que en la entrevista hablamos poco de Snowden y de Vigilancia permanente y la cosa se centra más en mis opiniones, pero creo que puede resultar interesante.

Acceso a la entrevista de una hora y veinte minutos de duración:

Gracias a Armando Silles por esta oportunidad. Por cierto, que ha publicado Aire verdadero, un libro de poesía bastante interesante que igual te apetece.