retour d'expérience
utilisation de Moose et DBIx::Class
projet d'administration système
mission de 3 mois chez eStat Médiamétrie
développement d'un outil de génération de configurations
source : RackTables
cibles : PXE, Kickstarter, Cfengine, Nagios, Cacti, Bacula, HP iLO, APC RackPDU, switch Cisco
logiciel de CMDB orienté datacenter
PHP/MySQL
pas au niveau de GLPI
interface claire
très adapté aux besoins IT
projet neuf, sans historique
notions d'objets avec propriétés transverses
pas de contrainte de performances
=> Moose
schéma de la base assez connecté
=> DBIx::Class
DBIx::Class::Schema::Loader
dbicdump -o dump_directory=lib -o preserve_case=1 RackTables \ dbi:mysql:racktables rackuser rackpass
génération dans lib/ d'une hiérarchie de modules immédiatement utilisable
use strict; use Socket; use YAML; use lib "lib"; use RackTables; my $name = shift; my $schema = RackTables->connection("dbi:mysql:racktables", "rackuser", "rackpass"); my $obj_rs = $schema->resultset("RackObject")->search( { "me.name" => $name }, { prefetch => { attribute_values => { attr => "attr" } } }, );
while (my $obj = $obj_rs->next) { # store object properties my %prop; $prop{$_} = $obj->$_ for qw< id name asset_no has_problems comment >; # resolve object type my $type_rs = $schema->resultset("Dictionary")->search( { chapter_id => 1, dict_key => $obj->objtype_id }, ); $prop{type} = $type_rs->first->dict_value; # find IPv4 addresses my $ipv4_rs = $schema->resultset("Ipv4Allocation")->search( { object_id => $prop{id} }, ); my @addrs = map +{ type => $_->type, iface => $_->name, addr => inet_ntoa(pack "N", $_->ip), }, $ipv4_rs->all; print YAML::Dump{ properties => \%prop, addresses => \@addrs }; }
noms de méthodes créés par DBIx::Class::Schema::Loader pas toujours évidents
comportement difficile à contrôler
Moose
encore mieux que ce qu'on en dit
RackMan::File - définition
package RackMan::File; use File::Basename; use File::Path; use Moose; use Path::Class; use RackMan; use namespace::autoclean; has name => ( is => "rw", isa => "Str" ); has path => ( is => "rw", isa => "Str" ); has content => ( is => "rw", isa => "Str" ); sub fullpath { ... } sub add_content { ... } sub read { ... } sub write { ... } __PACKAGE__->meta->make_immutable
RackMan::File - utilisation
composition dynamique de rôles
spécialisation progressive d'un objet
Device -> PDU -> APC_RackPDU -> Server -> HP_ProLiant -> Switch -> Cisco_Catalyst
RackMan::Device
sub BUILD { my ($self, $args) = @_; my %implemented = map { $_ => 1 } RackMan::Types->implemented; if ($implemented{$self->object_type}) { # determine the type of device and apply the corresponding # role to the object my $type_role = __PACKAGE__."::".$self->object_type; eval { Class::MOP::load_class($type_role) } or RackMan->error("can't load $type_role: $@"); $type_role->meta->apply($self); # "specialise" the object (apply a specialised role) to the object # so it knows how to speak with the actual hardware, if needed $self->specialise; } }
RackMan::Device::{PDU,Server,Switch}
use constant HW_ROLES => ( APC_RackPDU => qr/^APC\s+AP\d+/ ); use constant CONFIG_FORMATS => qw< Cacti Nagios >; has class => ( is => "rw", isa => "Str" ); sub specialise { my $self = shift; my %hw_roles = $self->HW_ROLES; # fetch the sub-roles patterns # determine the role corresponding to the hardware my $hw_type = $self->attributes->{"HW type"}; return unless $hw_type; my ($hw_role) = grep { $hw_type =~ $hw_roles{$_} } keys %hw_roles; if ($hw_role) { # load and apply the role to the object $hw_role = __PACKAGE__."::$hw_role"; $self->class($hw_role); eval { Class::MOP::load_class($hw_role) } or RackMan->error("can't load $hw_role: $@"); $hw_role->meta->apply($self); } }
commande rack
documentation
tests
informations :
rack info samus.example.com
différences entre les configs réelles et attendues :
rack diff samus.example.com
génère et écrit les fichiers de configuration :
rack write samus.example.com
envoie la configuration :
rack push samus.example.com