7.3. Definir e implementar la API

En este momento ya deberiamos haber adquirido un alto grado de conocimiento sobre el servicio con el que vamos a trabajar, y haber decidido qué vamos y qué no vamos a permitir realizar al futuro usuario del mismo. Además ya tenemos la plantilla básica sobre la que empezar a escribir nuestro módulo. Ahora debemos definir la API, ver que métodos vamos a necesitar para que el módulo pueda obtener y establecer los parámetros de configuración elegidos, asi como el manejo típico sobre el demonio: arranque, parada, reinicio, etc...

El backend de nuestro módulo va a estar en la clase EBox::NTP. Esta clase va a heredar de EBox::GConfModule y contendrá todos los métodos que forman el API del NTP. Este es su constructor:

Ejemplo 7.1. Constructor de EBox::NTP

sub _create 
{
	my $class = shift;
	my $self = $class->SUPER::_create(name => 'ntp', 
						domain => 'ebox-ntp',
						@_);
	bless($self, $class);
	return $self;
}

Tras el estudio realizado en la sección anterior podemos definir los siguientes métodos en EBox::NTP, recordemos que todo método privado de la clase ha de empezar por el carácter de subrayado (_metodo):

setService

Este método recibe un parámetro que establecerá la activación o no del servicio de sincronización horaria de eBox, con él permitiremos que un cliente de nuestra red pueda sincronizar su fecha y hora con eBox.

Veamos mas a fondo cómo está implementado este método:

Ejemplo 7.2. Activación del servicio de sincronización

sub setService 
{
	my ($self, $active) = @_;
	if ($active xor $self->service) {
		$self->set_bool('active', $active);
	}
}  

En primer lugar tomamos el valor de $active que recibimos como parámetro del métod y por otro lado gracias a la llamada a $self->service obtenemos el estado del servicio de sincronización. En caso de que estos valores sean diferentes pasamos a actualizar el valor del parámetro active de la configuración del módulo en gconf, mediante la llamada $self->setbool('active', $active).

service

En este caso, el método nos retornará el estado del servicio de sincronización horaria. La implementación de este método es trivial, tan solo retornamos el valor del parámetro active de la configuración del módulo almacenada en gconf.

Ejemplo 7.3. Obtención el estado del servicio NTP

sub service 
{
	my $self = shift;
	return $self->getbool('active');
}  

setSynchronized

Este método recibe un parámetro que permitirá o no la posibilidad de que eBox sincronice su fecha y hora a través de servidores externos NTP. Como vemos en el código que tenemos a continuación es practicamente similar a la implementación del método setService salvo que esta vez almacenamos el valor del parámetro synchronized en la configuración de gconf.

Ejemplo 7.4. Establecer el estado de la sincronización externa

sub setSynchronized # (synchronized)
{
	my ($self, $synchronized) = @_;

	if ($synchronized xor $self->synchronized) {
		$self->set_bool('synchronized', $synchronized);
	}
}  

synchronized

Éste método retorna el valor del parámetro synchronized de la configuración almacenada en gconf. Su implementación es muy sencilla y similar a la del método service.

Ejemplo 7.5. Obtener el estado de la sincronización externa

sub synchronized 
{ 
	my $self = shift;
	return $self->getbool('synchronized');
}  

setServers

Gracias a este método, vamos a poder almacenar en la configuración de gconf los servidores de NTP externos que el usuario introduzca en caso de que active el servicio de sincronización externa. La implementación de este método tiene en cuenta por un lado si ha introducido una dirección ip o un nombre dns para poder comprobar su sintaxis. En caso de que sea correcta, lo almacenará en gconf mediante el método set_string. Por otro lado también se ha de tener en cuenta que no vamos a permitir introducir un servidor secundario de ntp si antes no se ha introducido un primario, e igual para un tercero. Veamos parte[1]de la implementación de este método:

Ejemplo 7.6. Establecer servidores ntp externos

sub setServers # (server1, server2, server3) 
{
	my ($self, $s1, $s2, $s3) = @_;

	if ($s1 =~ /^(\d{1,3}\.){3}\d{1,3}$/) {
		checkIP($s1, __("primary server IP address"));
		$self->set_string('server1', $s1);
	} else {
		checkDomainName($s1, __("primary server name "));
		$self->set_string('server1', $s1);
	}

	if (defined($s2) and ($s2 ne "")) {
		if ($s2 =~ /^(\d{1,3}\.){3}\d{1,3}$/) {
			checkIP($s2, __("secondary server IP address"));
			$self->set_string('server2', $s2);
		} else {

			...
 

servers

Éste método nos retorna un array con los servidores ntp externos que tengamos almacenados en gconf. Su implementación es como sigue:

Ejemplo 7.7. Obtener la lista de servidores ntp externos

sub servers 
{
	my $self = shift;
	my @servers;
	@servers = ($self->get_string('server1'),
		$self->get_string('server2'),
		$self->get_string('server3'));

	return @servers;
}  

setNewDate

Con este método vamos a ofrecer la posibilidad de cambiar manualmente la fecha y hora del sistema. Veamos su implementación:

Ejemplo 7.8. Establecer una nueva fecha y hora en el sistema

sub setNewDate # (day, month, year, hour, minute, second)
{
	my ($self, $day, $month, $year, $hour, $min, $sec) = @_;

	my $newdate = "$year-$month-$day $hour:$min:$sec";
	my $command = "/bin/date --set \"$newdate\"";
	root($command);

	$self->_restartAllServices;
}  

Recibimos como parámetros todos y cada uno de los datos necesarios para establecer la fecha y hora el sistema: día, mes, año, hora, minutos y segundos.

Formamos el comando completo en la variable $command y realizamos una llamada al método root($command) con el comando formado. Este comando ha de ser llamado como root mediante sudo y es por eso que no lo ejecutamos directamente.

Tras un cambio de hora en el sistema es necesario reiniciar ciertos servicios como módulos de eBox y logs del sistemar; es por ello que realizamos la llamada al método _restartAllServices del que veremos su implementación mas adelante.

setNewTimeZone

Al igual que podemos modificar la fecha y hora del sistema, también vamos a poder cambiar la zona horaria, para ello nos implementamos éste método de la siguiente manera:

Ejemplo 7.9. Establecer una nueva zona horaria

sub setNewTimeZone # (continent, country)
{
	my ($self, $continent, $country) = @_;

	my $command = "ln -s /usr/share/zoneinfo/$continent/$country" .
			" /etc/localtime";
	$self->set_string('continent', $continent);
	$self->set_string('country', $country);
	root("rm /etc/localtime");
	root($command);
	$self->_restartAllServices;
} 

Vamos a recibir dos parámetros: el país y el continente. Con ellos vamos a rehacer el enlace simbólico de /etc/localtime para que apunte al nuevo país que se nos pasa como parámetro. Por ejemplo si como parámetros tenemos Dakar como país, y Africa como continente, /etc/localtime deberá apuntar a /usr/share/zoneinfo/Africa/Dakar.

También almacenaremos ambos parámetros en la configuración de gconf con las llamadas al método set_string. Tras haber realizado las llamadas a comandos del sistema para establecer la nueva zona horaria, pasamos a reiniciar ciertos módulos eBox y sistemas de logs para que se arranquen en un estado consistente y no trabajen con una desviación temporal.

_restartAllServices

Hay ciertas operaciones que realizan métodos del módulo que pueden dejar el sistema inconsistente, porque modifican la fecha y hora. Para que tanto módulos eBox como logs no se enfrenten a una diferencia temporal, vamos a implementar este método que reiniciara los sistemas de logs y módulos eBox que ahora se lanzarán con la nueva fecha y hora del sistema. Veamos como está implementado:

Ejemplo 7.10. Reiniciar módulos y servicios del sistema

 sub _restartAllServices
{
	my $self = shift;
	my $global = EBox::Global->getInstance();
	my @names = grep(!/^network$/, @{$global->modNames});
	@names = grep(!/^firewall$/, @names);
	my $log = $global->logger;
	my $failed = "";
 	$log->info("Restarting all modules");
	
	foreach my $name (@names) {
		my $mod = $global->modInstance($name);
		try {
			$mod->restartService();
		} catch EBox::Exceptions::Internal with {
			$failed .= "$name ";
		};
	}
	
	if ($failed ne "") {
		throw EBox::Exceptions::Internal("The following modules ".
			"failed while being restarted, their state is ".
			"unknown: $failed");
	}

	$log->info("Restarting system logs");
	try {
		root("/etc/init.d/sysklogd restart");
		root("/etc/init.d/klogd restart");
		root("/etc/init.d/cron restart");
	} catch EBox::Exceptions::Internal with {
	};
}  

Primero obtenemos una instacia de la clase EBox::Global con la que obtendremos la lista de módulos instalados en eBox. Vamos a reiniciar todos salvo los módulos network y firewall, capturando las posibles excepciones que se produzcan al reiniciar cada módulo. Tras ello pasaremos a reiniciar manualmente los sistemas de log: sysklogd, klogd y cron, que como requiere privilegios de root se realiza a través de la función root.



[1] La implementación completa de este método asi como del módulo completo se puede encontrar en el repositorio de subversion