Hoy voy a documentar esta pequeña guía que estoy haciendo para actualizar las máquinas virtuales de nuestro parque de laboratorio. Nosotros usamos máquinas virtuales del tipo OpenVZ, mediante un producto que se llama Proxmox VE. Esta plataforma de virtualización permite crear máquinas KVM para paravirtualización, y máquinas (o contenedores) OpenVZ para virtualización a nivel de sistema operativo (pinchad en los enlaces para más información).
Al ser contenedores, se pueden usar plantillas precreadas (al estilo vagrant) para desplegar nuestros servicios, pero la cosa últimamente va lenta y todavía no hay disponibles plantillas para ubuntu 12.10, así que otra opción es actualizar los sistemas.
El caso es que empezamos a actualizar las máquinas de versión ubuntu 11.10 a ubuntu 12.10 y he aquí que no hay mucho problema, pero querría dejar documentado el procedimiento para un futuro.
Pasos:
Actualizar todo
1
sudo apt-get update && sudo apt-get dist-upgrade
Instalación (si no lo tenías de antes) del paquete update-manager-core (proporciona do-release-upgrade)
1
sudo apt-get install update-manager-core
Actualización a ubuntu 12.04
1
sudo do-release-upgrade -m server
Este realiza varias tareas de sustitución de paquetes, y lo único más fastidioso es que no he encontrado cómo decirle que me acepte todas las respuestas por defecto, así que hay que hacerlo a mano.
Reinicio
1
sudo reboot
Editar el fichero /etc/update-manager/release-upgrade
1 2 3 4 5
cambiar
Prompt = lts
a
Prompt = normal
Actualización a ubuntu 12.10
1
sudo do-release-upgrade -m server
Vuelve a realizar los mismos pasos con preguntas interactivas.
Resolución de problemas
La actualización a ubuntu 12.10 da fallos porque la anterior medio configura grub, que es inútil en un contenedor. Al actualizar a la 12.10, ésta intenta borrar el kernel anterior, y previamente lo consulta a grub, así que para resolver el problema, hay que realizar los siguientes pasos:
Localizar y destruir grub en la máquina virtual
1
for i in `sudo dpkg -l | grep grub | awk '{print $2}'`; do echo $i; sudo dpkg --force-all -r $i; done
Continuar con la desinstalación del kernel antiguo
Como parte de nuestro proyecto BlueMountain ERP, tenemos gran cantidad de datos relacionales: tablas maestras, módulo de CRM, módulo de Servicios, etc. Para nuestro post, vamos a centrarnos en las relaciones entre un cliente del CRM y los servicios que ofrece la empresa que tenga instalado BlueMountain:
Personalizaciones de servicios por cada cliente.
Documentos asociados a cada servicio que se realice para el cliente (operación), los cuales son enviados a las autoridades aduaneras o compañías de transporte y posteriormente almacenados en un archivo documental.
Estos datos no tienen un esquema fijo, sino que su estructura está asociada al modelo que está detrás de cada formulario. Sin embargo, dentro de la estructura (documento) sí se establecen referencias a entidades puramente relacionales. Estas referencias las obtiene el usuario que rellena el formulario a través de autocompletes o combo boxes.
1 2 3 4 5 6 7 8 9 10 11 12
{
_id: ObjectId('....'), //identififador del documento
doc-type: 'soul selling contract',
official: true,
customer_id: 1337, // referencias a ids de MySQL
service_id: 42,
doc-data :{
...
...
}
...
}
Este documento se almacena en MongoDB, dentro de una colección determinada (por ejemplo ‘docs’). Mediante nuestra capa REST de acceso a datos, asignamos una URI a cada documento MiongoDB que almacenamos: http://bluemountain/data/nosql/docs/{_id} – de esta manera, los formularios con entidades relacionales pueden cargar de manera lazy los paths que hacen referencia a MongoDB:
¿Podríamos haber atacado este problema usando un modelo tradicional, basado en una base de datos relacional? Antes de contestar, veamos la siguiente presentación:
Para nosotros resulta natural usar una base de datos NoSQL para almacenar estos documentos JSON. Además, la cantidad de documentos a almacenar en un sistema en producción puede alcanzar perfectamente el millón anual. MongoDB parece la solución adecuada ya que nos ofrece gran escalabilidad y es muy fácil de integrar dentro de nuestra capa de acceso REST a datos (DataChannel).
Arquitectura del ERP Blue Mountain
Ahora bien, nuestro DataChannel es híbrido (usa una base de datos relacional y una base de datos documental). a la hora de diseñar la aplicación JavaEE, es muy importante ser lo más genéricos posibles:
Diagrama de Clases (work in progress)
Como puede verse, nuestras entidades usan JPA 2.0 (EclipseLink 2.2) como capa de persistencia relacional. De la parte NoSQL se encarga un EJB (3.1) con un DAO muy sencillito que envuelve al driver Java de MongoDB. Haber usado un Object-Document Mapper (ODM) como Morphia, habría sido una opción, pero la aparición de JPA 2.1 y EclipseLink 2.4 cambiarán las cosas, ya que por fin tendremos un framework de persistencia que hará a las entidades documentales ciudadanos de primera clase en el mundo de Java EE. Merece la pena esperar, un poco más hasta que podamos refactorizar nuestra solución.
En resumen, se puede decir que estamos guardando nuestros datos en un modelo híbrido que para nosotros (y para nuestra aplicación) tiene mucho sentido. Ahora bien, ¿alguien más ha hecho esto? ¿Qué nos recomienda la gente de 10gen?
En la presentación que acabamos de ver (link al webinar correspondiente), parece que es la propia aplicación cliente quien debe distinguir entre los recursos RDBMS y MongoDB. Por contra, en BlueMountain, gracias a nuestra aplicación de DataChannel, tenemos la ventaja de proporcionar un acceso unificado y agnóstico a cada repositorio de datos, a través de REST y JSON. Esto implica que es aquí donde debemos controlar aspectos claves de la integración de estos dos mundos:
Validación del schema JSON de los documentos MongoDB.
Reglas de integridad referencial entre los dos mundos.
Control de concurrencia (bloqueos y transacciones).
¡Búsquedas!
En los próximos días os iremos contando. ¡Permanezcan en sintonía!
Si has llegado hasta este tutorial, supongo que conocerás que hace JNI. Si no lo sabes pero tienes curiosidad haz click aquí.
En este tutorial veremos todo el código necesario para: crear una máquina virtual Java (JVM) y ejecutar una aplicación Swing o Java. Todo con jni desde C++. En nuestro caso, estamos preparando el lanzador de nuestra aplicación BlueMountain, basada en Swing a través de Scala.
Investigando…
El principal problema es que antes de ejecutar la aplicación, debemos comprobar que la máquina donde ésta se ejecuta cumple con los requisitos necesarios de memoria, CPU y resolución. Ciertos parámetros de tuning de la JVM cambiarán en función de estas condiciones.
Posteriormente, hay que comprobar la existencia de actualizaciones y reemplazar los JARs de dependencias en tal caso. Y después arrancar la aplicación. Además, por si fuera poco, Windows no permite poner “splash screen” en las aplicaciones GUI basadas en la JVM. Inicialmente, barajamos la posibilidad de usar empaquetadores como Launch4J, WinRun4J o JSmooth. Sin embargo, todos ellos parecen estar específicamente diseñados para Windows y no nos permitiría unificar el proceso de construcción y ejecución de BlueMountain.
AppBundler
Afortunadamente, encontramos un post de Josh Marinacci donde se menciona AppBundler. Esta “meta-aplicacion” construye bundles ejecutables en Mac y Windows. ¿Cómo lo logra? Bien, en Mac basta con jugar un poco con la estructura de ficheros y directorios; en Windows usa la anteriormente mencionada JSmooth. Es relevante mencionar que tuvimos que hacer nuestro propio fork de AppBundler, debido a que su soporte para Mac es limitado, se deben incluir algunas DLLs extra para Windows 7/Vista y no tiene la posibilidad de meter iconos ni splash screens.
AppBundler está bien si quieres distribuir tu aplicación y empaquetarla, por ejemplo, con NSIS. Además, podemos integrar todo en nuestros build scripts de Maven. Y así es como lo estuvimos haciendo durante un tiempo.
¿Pero por qué Qt, C++ y JNI?
Ni siquiera AppBundler nos puede ayudar demasiado. Básicamente por las cosas que hay que hacer antes de arrancar la aplicación en sí:
Comprobar requisitos del sistema.
Ajustar parámetros de la JVM en función de los mismos.
Actualizar JARs antes de lanzar la aplicación.
La estructura de ficheros de la aplicación debe ser homogénea.
Pasar parámetros a la aplicación desde un fichero de propiedades.
Ninguno de los proyectos que hemos buscado puede satisfacer todos estos puntos. Así que nos propusimos hacer una prueba de concepto basada en Qt, desde donde crearíamos la JVM y llamaríamos al main() de nuestra aplicación. Embebiendo código C++ y usando el (no demasiado) honorable JNI.
Comenzamos
Primero, asegúrate de tener tu JDK instalado:
Oracle JVM 7u5: soporta las optimizaciones para lenguajes dinámicos, opción que OpenJDK aún tiene que activar. Una de las incertidumbres a despejar en nuestro prototipo es saber si podremos usarlo con nuestro Scala/JRuby Swing GUI.
OpenJDK 1.7u3 también funciona, pero si vas a usar lenguajes dinámicos, recomendamos la de Oracle :-/
Show me the code!
¿Cómo crear la JVM y llamar a HolaMundoSwing desde C++? Sólamente usa el código C++ en tu main() de la aplicación Qt. Usa los binarios createAndShowGUI.class y HolaMundoSwing.class como Qt Resources.
Problemas que me he encontrado y podrias encontrarte
Google: información muy esparcida, anticuada, mezclada y trozos de código copypasteado no operativo. Espero que este post ayude a solucionar eso. De hecho, en poco más de un mes, la mejor fuente de información práctica ha sido esta entrada, que usa Visual C++ y genéricos para lanzar la JVM.
“No se encuentra jni.h”:
Verifica el path de tu sistema y asegurate que apunta al sitio donde esta tu jvm, yo por ejemplo en linux uso estas lineas:
Con esto el sistema ya sabe a donde tiene que ir a buscar “jni.h
“error: base operand of ‘->’ has non-pointer type ‘JNIEnv’”
Es debido a que has escrito (*env) en lugar de env, en algunos ejemplos de internet encontraras (*env), estos ejemplos están hechos con c, no con c++, para evitar este error en c++ simplemente quita los paréntesis y el *.
No se encuentra la clase:
Revisa el class.path que pasaste como option a la jvm y el nombre de la clase que le diste en FindClass().
En versiones antiguas de jni se usaba JDK1_1InitArgs si no te lo reconoce no te preocupes cámbialo por JavaVMInitArgs.
El array de objetos que contiene los argumentos esta declarado. Le he metido los valores pero no envia todos los valores solo el primero o los dos primeros y tengo 32 argumentos que le paso al main. Puede que sea por esta linea:
El número que ves en esta linea es el tamaño del array de objetos, aunque metas 32 objetos en el array si solo esta dimensionado a 3 solo mandara los 3 primeros elementos.
No me da fallo al compilar, ni al lanzar, funciona si la aplicación es por linea de comandos pero no se muestra la aplicación en Swing.
Cuando llamas al método main() de java desde JNI, le estas diciendo a JNI algo así como “quédate esperando hasta que acabe el método al que has llamado y luego sigue con tu ejecución”, de modo que cuando tu llamas a un main() donde la acción se ejecuta en el main thread, todo funciona por que hasta que el main thread no acabe no acabara la llamada JNI.
Al usar una interfaz Swing, esta no se crea en el main thread sino en otro thread, de modo que JNI llama a la clase main(), ejecuta el main thread y, como no hay nada, acaba la llamada y destruye la JVM.
Solución: yo lo arreglé haciendo la interfaz Swing en un GUI thread aparte, el cual arranco en el main. Basta con hacer un join() sobre el thread para que el main thread tenga que esperar a que el GUI thread acabe de ejecutarse para seguir ejecutándose.
Este post va sobre cómo sustituir nuestros scripts old school por algo con un poco más de estilo. ¿Por qué? Básicamente, aunque internamente sigan realizando las mismas acciones que nuestros shell, perl, whatsoever scripts, hay veces que cuando necesitas estandarizar un script para que otras personas puedan mantenerlo, se necesita un toque de encanto.
En esta ocasión, debido al tiempo que le estoy dedicando, voy a hablar de rake.
Rake, como muchas herramientas del mundo make, permite realizar varias acciones con una sintaxis específica. Lo que me ha gustado de rake ha sido:
no es XML (chúpate esa, ant)
tiene su propio léxico para estructurar la información
puedes usar la potencia del lenguaje de programación ruby internamente
puedes ejecutar shell script de forma fácil y cómoda
Por ello, cuando me he visto en la necesidad de describir a mis compañeros qué hace un script de despliegue de actualizaciones, he decidido pasar de documentar mi shell script (con sus funciones y sus cases) y sustituirlo por namespaces y tareas.
Por ejemplo, si tenemos un entorno con varios servicios sobre los que operar, podemos mapear los servicios a namespaces y las acciones asociadas a un servicio, a tareas, de forma que ejecutar tareas comunes se vuelva más simple y sencillo:
1 2 3 4 5 6 7
namespace :serviciodo
desc "Describe accion a realizar"
task :task1do
sh "do shell things" puts"do ruby things".upcase! end end
Al ejecutar rake -T la salida será bastante elegante:
1 2
$ rake -T
rake servicio1:task1 # Describe accion a realizar
Otra cosa bastante interesante de rake es su facilidad para acceder a variables que se le pueden pasar del entorno o a través de la línea de comandos, permitiendo crear comportamiento dinámico en base a los argumentos de la linea de comandos:
1 2 3 4
desc "Say something (needs RAKE_MSG variable)"
task :saydo puts"#{ENV['RAKE_MSG']}" end
1 2
$ rake servicio1:say RAKE_MSG="Hola Mundo"
Hola Mundo
Con la capacidad de rake de ejecutar comandos shell, podemos, entre otras cosas, ejecutar acciones remotas con ssh, como indican en el libro “Puppet 2.7 Deploying changes with Rake”. Es bastante cómodo:
1 2 3 4 5
desc "Conectar a la máquina remota y lanzar una actualización de puppet (necesita variable RAKE_HOST)"
task :launch_puppetdo
SSH='ssh -t -A'
sh "#{SSH} #{ENV['RAKE_HOST']} puppet agent --test " end
Rake es lo suficiente flexible para poder, por ejemplo, delimitar la ejecución de rake a que se cumpla algún requisito, como que se ejecute con permisos de root:
1 2 3 4
if`id -u` != '0'then puts"Error: debes ser root para ejecutar este comando" exit1 end
Al intentar ejecutar como un usuario diferente de root, aparecería lo siguiente:
1 2
$ rake -T
Error: debes ser root para ejecutar este comando
Otra característica que me ha gustado mucho es la facilidad para agregar tareas, con dos sintaxis, una comprimida y otra más extensa:
1 2
desc "Aggregation 1: Restart the service and check it"
task :restart => [:stop, :start, :check]
1 2 3 4 5 6 7
namespace :alldo
desc "Aggregation 2: Execute some tasks"
task :updatedo Rake::Task['servicio1:restart'].invoke Rake::Task['servicio2:restart'].invoke end end
Esto permite lanzar varias tareas conjuntamente, y con el orden bastante claro.
Y así, poco a poco, tu Rakefile toma pinta de plantilla, de forma que sea fácilmente reproducible:
namespace :serviciodo
desc "Start the service"
task :startdo
sh "service XXX start" end
desc "Stop the service"
task :stopdo
sh "service XXX stop" end
desc "Check the service is up and running"
task :checkdo # Do some checks
sh "netstat -tunpl|grep 8080" end
desc "Aggregation 1: Restart the service and check it"
task :restart => [:stop, :start, :check]
end
namespace :alldo
desc "Aggregation 2: Execute some tasks"
task :updatedo Rake::Task['servicio1:update'].invoke Rake::Task['servicio2:update'].invoke end end
El post de hoy se lo dedico a un proyecto que me ha resuelto un gran problema que llevo arrastrando mucho tiempo: el de la gestión de las JVM de Oracle Sun en Debian y Ubuntu. Desde que en el año 2011 Oracle cortó el soporte a la distribución de sus paquetes de java, los que utilizamos su JVM andamos fastidiados realizando instalaciones con tar.gz o peor, ejecutando scripts autoextraibles. Automatizar esta tarea es un infierno, más aún cuando Java7 arrastra nombres con fechas, versiones y subversiones, y Java6 han reforzado el script para que tengas que aceptar la licencia manualmente al final de la extracción del paquete.
Pero hoy he estado jugando con un proyecto que me ha simplificado la vida como nunca nadie lo había hecho antes :-)
Se trata de un proyecto de github llamado oab-java de Martin Wimpress, http://flexion.org, que permite descargar, descomprimir, empaquetar y meter en un repositorio APT, varios paquetes Java. No sólo se descarga automáticamente la última versión, sino que además te genera paquetes .deb para tu instalación. Maravilloso!
Es recomendable ejecutarlo en una máquina virtual para que no nos enguarre mucho nuestro sistema, y una vez hecho estó, podemos ejecutarlo fácilmente con:
$ sudo ./oab-java.sh
Qué realiza el script? Pues fusilando la documentación del proyecto, ejecuta las siguientes acciones:
This script is merely a wrapper for the most excellent Debian packaging scripts prepared by Janusz Dziemidowicz.
Build the Java packages applicable to your system.
Create local apt repository in /var/local/oab/deb for the newly built Java Packages.
Create a GnuPG signing key in /var/local/oab/gpg if none exists.
Sign the local apt repository using the local GnuPG signing key.
El resultado final es un repositorio local en /var/local/oab con el directorio deb como contenedor del repositorio:
1 2 3 4 5
oab
├── deb
├── gpg
├── pkg
└── src
Los paquetes que genera son por defecto para la versión 6 de Java:
sun-java6_6.33-2~oneiric1_amd64.changes
sun-java6-bin_6.33-2~oneiric1_amd64.deb
sun-java6-fonts_6.33-2~oneiric1_all.deb
sun-java6-javadb_6.33-2~oneiric1_all.deb
sun-java6-jdk_6.33-2~oneiric1_amd64.deb
sun-java6-jre_6.33-2~oneiric1_all.deb
sun-java6-plugin_6.33-2~oneiric1_amd64.deb
sun-java6-source_6.33-2~oneiric1_all.deb
Pero si volvemos a ejecutar el mismo script con el parámetro -7 ($ sudo ./oab-java.sh -7), se generarán también los de Java7:
ia32-oracle-java7-bin_7.5-2~oneiric1_amd64.deb
ia32-sun-java6-bin_6.33-2~oneiric1_amd64.deb
oracle-java7_7.5-2~oneiric1_amd64.changes
oracle-java7-bin_7.5-2~oneiric1_amd64.deb
oracle-java7-fonts_7.5-2~oneiric1_all.deb
oracle-java7-javadb_7.5-2~oneiric1_all.deb
oracle-java7-jdk_7.5-2~oneiric1_amd64.deb
oracle-java7-jre_7.5-2~oneiric1_all.deb
oracle-java7-plugin_7.5-2~oneiric1_amd64.deb
oracle-java7-source_7.5-2~oneiric1_all.deb
Como veis, se generan para todas las plataformas x86, la de 32 y la de 64 bits, incluyendo los paquetes de soporte a librerías de 32 para las máquinas de 64 bits. UNA PASADA!
Por último, cada paquete que instalemos, se instalará en el directorio por defecto de linux para las JVM (/usr/lib/jvm/java-6-sun/), instalará las dependencias correspondientes y ejecutará el comando update-alternatives que nos permitirá trabajar con la versión sin necesidad de configurar inicialmente las famosas variables de entorno JAVA_HOME. WOWOWOW!
Ante esta maravilla de proyecto, sólo queda decir: GOOD WORK!
Para generar paquetes .deb válidos para debian o ubuntu que podamos utilizar en nuestros despliegues, necesitamos una solución fácil y sencilla, integrable con nuestro sistema de continuous delivery. En esta ocasión vamos a usar maven: http://maven.apache.org/Ventajas para meternos a usar maven aquí? Veamos:
En abstra usamos maven para construir el software
También usamos Jenkins para construir nuestro software, con lo que usar maven simplifica la tarea de Jenkins y la hace más entendible al desarrollador
Las dependencias de maven se centralizan en un repositorio nexus (proxy-cache para maven)
Necesita menos requisitos en la máquina que se ejecuta
plugin de ant (maven-antrun-plugin) para crear una tarea que se descargue el software de la red y lo desempaquete en el directorio target durante la fase de process-resources
plugin (unix-maven-plugin) específico para la construcción de paquetes debian, rpm, pkg, y otros.
El pom.xml describe las acciones a realizar durante las fases de construcción:
Durante la fase de process-resources, ant descarga el software y lo descomprime.
Durante la fase de package, se ensambla el paquete debian, indicando varias acciones
Copiar el directorio o directorios a los directorios de destino (ej: /opt/sonar)
Copiar los ficheros necesarios para el arranque o establecer los enlaces necesarios para lo mismo
El unix-maven-plugin es bastante práctico resolviendo una cuestión que se nos había quedado anteriormente: cómo meter los scripts de postinstalación. La documentación especifica que por defecto lee del directorio src/main/unix/scripts los scripts en base a cómo sea el paquete a generar. Aquí pongo un enlace a la parte de la documentación que habla más sobre este asunto.
Ficheros:
Procedimiento:
mvn clean package
Se lee el pom y comienza con la limpieza del directorio target si existe
A continuación comienza con las fases de construcción de maven, en este caso principalmente son dos: process-resources y package
Durante la fase process-resources se descarga los plugins, las dependencias para maven y ant, y el software que vamos a paquetizar
A continuación pasa por el resto de fases sin hacer nada, hasta que llega a package
Comienza a copiar el directorio al destino final
Realizamos las acciones adicionales que queramos realizar: copiar ficheros, directorios, librerías, enlazar ficheros
Añade automáticamente los scripts de control al paquetizar
Genera el paquete
Termina
Y con esto ya tendríamos en este caso nuestro .deb listo para ser utilizado