domingo, 23 de noviembre de 2014

Jenkins en alta disponibilidad


Introducción


En esta entrada se explica cómo instalar un entorno del servidor de integración continua Jenkins en HA, de forma que se tengan preparados dos Jenkins Master.

La configuración descrita es de tipo activo-pasivo y utiliza la tecnología HAproxy.

Para ello se van a necesitar 3 máquinas. 

  • Máquina haproxy: 
    • Sistema operativo CentOS 6.5
    • Software necesarios:
      • haproxy
    • IP: 192.168.1.50
  • Máquina jenkins1: 
    • Sistema operativo CentOS 6.5
    • Software necesario:
      • jenkins.
      • java 7 oracle.
  • Máquina jenkins2: (Igual que la máquina jenkins1)

 Instalación de jenkins

Las máquina jenkins1 y jenkins2 deben tener instalado Java7 de oracle, para este punto se puede seguir cualquiera de los tutoriales que se pueden encontrar por internet, por ejemplo este.

Para la instalación de Jenkins se puede seguir los pasos descritos en la propia página de Jenkins. (En la página instalan jdk6, es preferible instalar jdk7).

Los pasos descritos básicamente consisten en ejecutar estos 3 comandos:



wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo  
rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key  
yum install jenkins  


Una vez instalado se puede especificar que se inicie el servicio al arrancar la máquina y arrancar el servicio:


 chkconfig jenkins on  
 service jenkins start  


Si no se le especifica lo contrario la home de jenkins está en el directorio /var/lib/jenkins .


Instalación y configuración HAproxy


HAproxy está dentro de los repositorios de CentOS con lo cuál su instalación es muy sencilla, simplemente consistes en ejecutar la siguiente instrucción:


 yum install haproxy  


El fichero de configuración de haproxy es el fichero /etc/haproxy/haproxy.cfg , se recomienda hacer una copia del fichero original:


 cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.orig  

Y sustituir el contenido de este por:


 global  
   log   127.0.0.1 alert  
   log   127.0.0.1 alert debug  
   maxconn 4096  
 defaults  
   log              global  
   mode              http  
   option             httplog  
   option             dontlognull  
   option             redispatch  
   retries            3  
   maxconn            2000  
   timeout connect     5000  
   timeout client     50000  
   timeout server     50000  
 ####################################  
 #  
 #      loadbalancer  
 #     192.168.1.50:8555  
 #      /     \  
 #  Jenkins1      Jenkins2  
 # 192.168.1.51:8080   192.168.1.52:8080  
 #  (active)      (passive)  
 #  
 ####################################  
 listen jenkinsfarm 192.168.1.50:8555  
   mode  http  
   stats  enable  
   balance roundrobin  
   option httpclose  
   option forwardfor  
   option httplog  
   option httpchk  GET /  
   server Jenkins1 192.168.1.51:8080 check inter 5000 downinter 500  # active node  
   server Jenkins2 192.168.1.52:8080 check inter 5000 backup      # passive node  

Como se puede ver, con la configuración realizada en haproxy, la máquina 192.168.1.50 (haproxy), hace de balanceador entre los dos máquinas jenkins, de forma que, si desde un navegador se va a http://192.168.1.50:8555 , haproxy ira contra jenkins1, si esta no responde realizará la petición al server backup, que es jenkins2.

Para poder realizar la prueba solo es necesario iniciar el servicio de haproxy:


 service haproxy start  

Y activarlo al iniciar la máquina:


 chkconfig haproxy on  


Configuración de los discos

 Jenkins guarda toda la información de las construcciones en disco, este punto es muy importante, porque con la configuración descrita, cada jenkins master tiene su propia información de las construcciones, en un directorio local de cada máquina "/var/lib/jenkins"

Para solucionar este problema, se debe especificar una variable JENKIS_HOME que apunte a un directorio compartido entre los dos jenkins. Por ejemplo un NFS distribuido.

Una solución menos elegante, es ir sincronizando los directorios home de la dos máquinas.

Visualizar el estado de los servidores


HAproxy dispone de una página donde se muestra el estado de los servidores, la URL para este ejemplo es http://192.168.1.50:8555/haproxy?stats

En el caso de que jenkins1 este iniciado y jenkins2 parado, se vería algo así:




Solución empresarial Jenkins HA

Una solución empresarial para disponer de Jenkins en alta disponibilidad, es la que ofrece ClouBees, con su plugin High Availability Plugin.

Destacar que CloudBees ofrece muchas funcionalidades en su versión empresarial de Jenkins.


viernes, 8 de noviembre de 2013

TDD y los test de integración

La práctica de programación Test-driven Development (TDD), desarrollo guiado por pruebas, está basada en la iteración continua de 5 pasos:

  • Escribir una prueba para un requisito.
  • Verificar que la prueba falla.
  • Escribir la implementación más simple para cumplir el requisito.
  • Ejecutar las pruebas automatizadas.
  • Refactorizar código.


Con la repetición de estos pasos conseguimos que la aplicación que se escribe únicamente cumpla con los requisitos que nos han pedido, con lo cual vamos directo al grano, no escribimos más código del que toca, diseñamos la aplicación en el mismo momento que escribimos las pruebas y el código de la misma, el código resultante suele ser muy legible y entendible ya que el paso de refactorización ayuda precisamente a esto, conseguimos una gran cobertura, los test cubren un porcentaje altísimo del código, entre el 90 y el 100%, conseguimos descartar test que no aportan nada y por supuesto es un mecanismo crucial para detectar errores de regresión.

Esta claro que aporta mucho para poder realizar una buena aplicación, pero como todo tiene sus limitaciones, hay que recordar que la base en la práctica del TDD son los test unitarios, no  los test de integración y mucho menos los test de interfaz gráfica o funcionales, esto quiere decir que sea lo que se que estemos implementando debe poder probarse de forma unitaria, de aquí las limitaciones de TDD con aplicaciones que se integran con bases de datos, con objetos o sistemas distribuidos “externos”, o incluso con la propia interfaz gráfica de la aplicación. 

Está claro que con el uso de stubs o mock objects podemos hacer todo pero hay que plantearse si el esfuerzo que conlleva escribir un test para estos casos no es demasiado elevado, o bien si este test lo podemos suplir mediante un test de integración más sencillo de escribir.

Entonces si es muy costoso realizar un test unitario puede que el mejor camino sea realizar el test de integración directamente. Esto puede que rompa algo la filosofía de TDD pero creo que es más práctico, incluso pienso que si el test de integración se ejecuta en tiempos similares a los unitarios el uso en conjunto con la práctica de TDD es factible. Al fin y al cabo se trata de ser prácticos.

viernes, 8 de marzo de 2013

MoveScript: Plugin para Jira





Hace más o menos un año, en la empresa donde trabajo, tuvimos la necesidad de realizar cierta lógica adicional a la función move de Jira.

La función move no hace otra cosa que mover una issue de un proyecto a otro, pero puede resultar interesante que además de realizar esta funcionalidad se requiera, por ejemplo añadir un comentario, modificar el assignee o ciertos campos de la issue, etc... Por este motivo realizamos una modificación en el funcionamiento del move. Actualmente, ya conociendo la API de desarrollo de Jira mucha más, y con el objetivo de introducirme en el mundo de desarrollo de Atlassian, he realizado un plugin que permite escribir un script usando groovy que se ejecute justo después de realizar el move y teniendo la información de la issue que se esta cambiando de proyecto.

En realidad no es nada más que un listener que cuando se lanza un move ejecuta un script groovy, tengo que decir que esto no hubiera sido posible sin antes haber conocido el plugin Script Runner, que fue el que despertó mi curiosidad por el mundo groovy.

Todo aquel que necesite utilizar esta funcionalidad, puede descargar el plugin en el market de Atlassian:  Move Script 

domingo, 20 de enero de 2013

Groovy y Java - Embeded groovy

Un punto que considero muy interesante es el poder ejecutar scripts o clases groovy desde java, por este motivo he estado realizando algunas pruebas.

La primera prueba que he realizado ha sido la de ejecutar un método de una clase groovy dentro de un main de java.

En concreto he creado la clase "HelloGroovy.groovy":

package main

class HelloGroovy {
 
 
 public String getHello(){
  return "Hello from groovy class";
 }
}
Y después una clase Java que llama al método "getHello", la clase "EmbeddedGroovy.java":


package main
package main;

import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;

import java.io.File;

public class EmbeddedGroovy {
 
 
 public static void main(String args[])throws Exception{
  ClassLoader parent = EmbeddedGroovy.class.getClassLoader();
  GroovyClassLoader loader = new GroovyClassLoader(parent);
  Class helloGroovyClass = loader.parseClass(new File("src/main/HelloGroovy.groovy"));
  GroovyObject groovyObject = (GroovyObject) helloGroovyClass.newInstance();
  Object[] arguments = {};
  String hello = (String)groovyObject.invokeMethod("getHello", arguments);
  System.out.println(hello);
 }

}


Como se puede apreciar esta llamada es muy parecida a la que se haría si la clase en lugar de ser de tipo groovy fuera java, pero existen otras manera de invocar a scripts groovy muy interesantes por ejemplo utilizando GroovyScriptEngine, de esta manera se puede ejecutar un script groovy dentro de una clase java tal y como se muestra en el siguiente ejemplo.

Script groovy hello.groovy:

println("Hello World");

Y la clase java ExecuteGroovyScript.java:


package main;

package main;

import groovy.lang.Binding;
import groovy.util.GroovyScriptEngine;

public class ExecuteGroovyScript {
 
 public static void main(String args[])throws Exception{
  String[] scripts = new String[] { "src/main" };
  GroovyScriptEngine gse = new GroovyScriptEngine(scripts);
  Binding binding = new Binding();
  gse.run("hello.groovy", binding);
 }

}

Al ser totalmente interpretado dota a java de la posibilidad de modificar script groovy y afectar al programa que lo incorpora sin necesidad de compilar, es más tal y como se muestra en la página http://groovy.codehaus.org/Embedding+Groovy existe la posibilidad de pasar parámetros a los scripts.
En la página mencionada anteriormente también describe un ejemplo básico utilizando la shell de groovy.

sábado, 28 de enero de 2012

Outlook to Jira AddIn

He visto que existen algunos desarrollos para poder añadir comentarios y ficheros desde el cliente de correo  a Jira, pero no he encontrado ninguno que lo haga en OutLook. Esto y las ganas de programar algo en c sharp fue lo que me motivó para escribir un "AddIn" para OutLook que haga precisamente esta funcionalidad.

Primero de toda aclarar que no uso c sharp y es lo primero que escribo en este lenguaje, por eso no cumplo ningún patrón, no se cómo se debe estructurar el código, no he leído ningún manual al respecto y simplemente me puse a programar de forma desordenada y buscando vía google las dudas que iban surgiendo según iba desarrollando el "AddIn", por eso, y ya que publico el código animo a todo el que quiera a pulir y añadir funcionalidades con la única condición que las comparta. (Una funcionalidad interesante sería la de crear issues a partir de un mail)

El AddIn tiene un aspecto similar al plugin de thunderbird, esto es debido a que vi el funcionamiento del plugin de thunderbird y decidí hacer algo parecido.

Aquí dejo algunos pantallazos del AddIn, el código fuente y el binario.




Links:

OutLookToJiraAddIn_0.2_alpha_src.zip
OutLookToJiraAddIn_0.2_alpha_bin.zip

lunes, 19 de diciembre de 2011

Introducción JPA- Java Persistence API


Hace algún tiempo había leído bastante sobre JPA, incluso antes de que naciera JPA tuve que trabajar con EJB 2.0 así que una de las cosas interesantes sobre ORM (Object Relational Mapping) están en JPA.


Con EJB 2.0 resultaba bastante incómodo trabajar con los ficheros xml de configuración, las diferentes interfaces local, remota y el propio bean, con JPA y las anotaciones de JAVA esto desaparece, pero no voy a correr tanto, en este artículo me voy a limitar a hablar de JPA, usando un ejemplo sencillo y usando la implementación que hace hibernate de JPA.


He utilizado la siguiente tecnología:
  • MySQL 5.1.xx
  • MySQL workbench
  • Eclipse Indigo
  • JAVA SE 7
  • JBOSS 5.0.1


Definición:


Antes de nada comentar que es JPA. Según la definición de la wikipedia:


Es la API de persistencia desarrollada para la plataforma Java EE
Es un framework del lenguaje de programación Java que maneja datos relacionales en aplicaciones usando la Plataforma Java en sus ediciones Standard (Java SE) y Enterprise (Java EE).
La JPA fue originada a partir del trabajo del JSR 220 Expert Group. Ha sido incluida en el estándar EJB3.
Persistencia en este contexto cubre tres áreas:
  • La API en sí misma, definida en javax.persistence.package
  • La Java Persistence Query Language (JPQL)
  • Metadatos objeto/relacional
El objetivo que persigue el diseño de esta API es no perder las ventajas de la orientación a objetos al interactuar con una base de datos (siguiendo el patrón de mapeo objeto-relacional), como sí pasaba con EJB2, y permitir usar objetos regulares (conocidos como POJOs).






El ejemplo:


Aunque he empezado primero creando las tablas en un esquema “JPA”, dentro de mysql, podría haber empezado cranto los beans de java y con estos establecer una conexión a MySql y se habrían creado de todas formas.


  • He creado las siguientes tablas con MySQL workbench (conectando a MySQL y generando las tablas):

  • Usando las funciones JPA de eclipse, he creado un proyecto de tipo JPA, y creado los beans entity usando la función “JPA entities from tables”, al final el código generado es el siguiente:


Departament.java:


package jpa.beans;
import java.io.Serializable;
import javax.persistence.*;
import java.util.Set;
/**
* The persistent class for the Departament database table.
*
*/
@Entity
public class Departament implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private int idDepartament;
private String name;
//bi-directional many-to-one association to Person
@OneToMany(mappedBy="departament")
private Set<Person> persons;
public Departament() {
}
public int getIdDepartament() {
return this.idDepartament;
}
public void setIdDepartament(int idDepartament) {
this.idDepartament = idDepartament;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Set<Person> getPersons() {
return this.persons;
}
public void setPersons(Set<Person> persons) {
this.persons = persons;
}
}


Person.java:


package jpa.beans;
import java.io.Serializable;
import javax.persistence.*;
/**
* The persistent class for the Person database table.
*
*/
@Entity
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
@EmbeddedId
private PersonPK id;
private String address;
private String country;
@Column(name="`first name`")
private String first_name;
@Column(name="`last name`")
private String last_name;
@Column(name="`zip code`")
private String zip_code;
//bi-directional many-to-one association to Departament
@ManyToOne
@JoinColumn(name="Departament_idDepartament",insertable=false,updatable=false)
private Departament departament;
public Person() {
}
public PersonPK getId() {
return this.id;
}
public void setId(PersonPK id) {
this.id = id;
}
public String getAddress() {
return this.address;
}
public void setAddress(String address) {
this.address = address;
}
public String getCountry() {
return this.country;
}
public void setCountry(String country) {
this.country = country;
}
public String getFirst_name() {
return this.first_name;
}
public void setFirst_name(String first_name) {
this.first_name = first_name;
}
public String getLast_name() {
return this.last_name;
}
public void setLast_name(String last_name) {
this.last_name = last_name;
}
public String getZip_code() {
return this.zip_code;
}
public void setZip_code(String zip_code) {
this.zip_code = zip_code;
}
public Departament getDepartament() {
return this.departament;
}
public void setDepartament(Departament departament) {
this.departament = departament;
}
}
PersonPK.java:
package jpa.beans;
import java.io.Serializable;
import javax.persistence.*;
/**
* The primary key class for the Person database table.
*
*/
@Embeddable
public class PersonPK implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
private int idPerson;
@Column(name="Departament_idDepartament")
private int departament_idDepartament;
public PersonPK() {
}
public int getIdPerson() {
return this.idPerson;
}
public void setIdPerson(int idPerson) {
this.idPerson = idPerson;
}
public int getDepartament_idDepartament() {
return this.departament_idDepartament;
}
public void setDepartament_idDepartament(int departament_idDepartament) {
this.departament_idDepartament = departament_idDepartament;
}
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof PersonPK)) {
return false;
}
PersonPK castOther = (PersonPK)other;
return
(this.idPerson == castOther.idPerson)
&& (this.departament_idDepartament == castOther.departament_idDepartament);
}
public int hashCode() {
final int prime = 31;
int hash = 17;
hash = hash * prime + this.idPerson;
hash = hash * prime + this.departament_idDepartament;
return hash;
}
}
  • Hice un pequeño Main de pruebas:


package jpa.main;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import jpa.beans.Departament;
public class Main {
public static void main(String args[])throws Exception{
EntityManagerFactory emf=Persistence.createEntityManagerFactory("JPATestJv");
EntityManager em=emf.createEntityManager();
try{
EntityTransaction entr=em.getTransaction();
entr.begin();
Departament dep = new Departament();
dep.setIdDepartament(0);
dep.setName("Marketing");
dep.setPersons(null);
em.persist(dep);
entr.commit();
}finally{
em.close();
}
}
}
  • El fichero persistence.xml:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="JPATestJv" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>jpa.beans.Departament</class>
<class>jpa.beans.Person</class>
<class>jpa.beans.PersonPK</class>
<properties>
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/JPA"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
<property name="hibernate.connection.password" value="JPA"/>
<property name="hibernate.connection.username" value="JPA"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>




  • El resultado de la ejecución del Main:


Hibernate: insert into Departament (name, idDepartament) values (?, ?)


  • Después se realiza la select para ver si se han insertado los datos:


mysql> select * from Departament;
+---------------+-----------+
| idDepartament | name |
+---------------+-----------+
| 0 | Marketing |
+---------------+-----------+
1 row in set (0.00 sec)

domingo, 11 de diciembre de 2011

La combinación de bibliotecas compartidas y extendedDocumentRoot en WebSphere 6.1




WebSphere 6.1 es un servidor de aplicaciones java quizá un poco sobre cargado en funciones y en diseño, pero entre esta funciones existen algunas que me ha resultado interesantes en el momento que tenia la necesidad de compartir páginas JSP y añadir al classloader del módulo WAR algunos JARS. La combinación de estas dos tecnologías permite crear funcionalidades fuera del contexto web y accesibles desde éste, en definitiva permite compartir recursos entre contextos.

Por una lado se debe utilizar la propiedad de motos de JSP “extendedDocumentRoot”, que tal cómo se indica en la ayuda infocenter de WebSphere v6.1:

extendedDocumentRoot:
Para compartir un recurso de archivo JSP en todos los archivos de aplicación web, especifique una lista de valores separados por comas de directorios o archivos de archivado Java (JAR) o ambos, como vías de acceso de búsqueda a utilizar si el recurso solicitado no se encuentra en el árbol público de documentos del archivo de la aplicación web. Si la petición es una petición parcial válida para un archivo de bienvenida, se devuelve un error 404. Si el archivo JSP se encuentra dentro de un archivo JAR y reloadEnabled es true, se utilizará la indicación de la hora del archivo JAR para las comprobaciones de isOutDated a efectos de recompilación. El valor por omisión de este parámetro es nulo.”

En pocas palabras, dentro del fichero “ibm-web-ext.xmi”, se añade la propiedad de motor de JSP “extendedDocumentRoot” que apunte a un directorio contenedor de JSPs, y el resultado es que estás JSPs són accesibles desde el contexto web.

Ejemplo de fichero “extendedDocumentRoot”:



<?xml version="1.0" encoding="UTF-8"?>
<webappext:WebAppExtension xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:webappext="webappext.xmi" xmi:id="WebAppExtension_1323629617174" reloadInterval="3" reloadingEnabled="true" additionalClassPath="" fileServingEnabled="true" directoryBrowsingEnabled="false">
<webApp href="WEB-INF/web.xml#WebApp_ID"/>
<jspAttributes xmi:id="JSPAttribute_1"
name="extendedDocumentRoot" value="/opt/IBM/WebSphere/jsps"/>

<jspAttributes xmi:id="JSPAttribute_2"
name="trackDependencies" value="true"/>
</webappext:WebAppExtension>


Nota: Además de la propiedad extendedDocumentRoot, se ha utlizado la propiedad trackDependencies, en este caso útil si se quieren realizar includes estáticos de JSP y se pretende forzar la compilación siempre.



Bien, una vez comentado cómo compartir páginas es interesante comentar cómo añadir conjuntos de JARs, bibliotecas compartidas, al classloader de módulo (WAR).
 Para ello se puede utilizar la funcionalidad de biblioteca compartida que implementa WebSphere. Hay que comentar que una biblioteca compartida puede estar a nivel de classloader de servidor, de aplicación EAR o de módulo web (WAR), en este caso resulto muy útil utilizarlo a nivel de WAR.

Lo primero y antes de seguir es importante definir que es una biblioteca compartida:


Las bibliotecas compartidas son archivos utilizados por varias aplicaciones. Toda biblioteca compartida consta de un nombre simbólico, una classpath de Java y una vía de acceso original para cargar las bibliotecas JNI (Java Native Interface). Puede utilizar las bibliotecas compartidas para reducir el número de archivos de biblioteca duplicados en el sistema. “

En definitiva, una biblioteca compartida no es nada más que un nombre lógico asociado a un classpath que contiene uno o varios JARs y estos son accesibles desde las diferentes aplicaciones, contextos o servidores que en su definición dicen utilizar esta biblioteca.

Para definir una biblioteca compartida se puede utilizar la consola administrativa de WAS:

Entorno > Bibliotecas compartidas > nombre_biblioteca_compartida.

Y para determinar por ejemplo que un módulo web utiliza la biblioteca:

  • Pulse Aplicaciones > Aplicaciones de empresa > nombre_aplicación >Referencias de bibliotecas compartidas en el árbol de navegación de la consola para acceder a la página de bibliotecas compartidas.
  • En la página Referencias de bibliotecas compartidas, seleccione una aplicación o un módulo con los que desee asociar una biblioteca compartida.
(http://publib.boulder.ibm.com/infocenter/wasinfo/v6r1/index.jsp)

Combinando estas dos funcionalidades se puede conseguir las páginas JSP de “extendedDocumentRoot” tengan funcionalidades implementadas dentro de una biblioteca compartida y todo esto accesible desde los contextos que lo quieran utilizar y sin necesidad de estar desplegado en el propio módulo.