#!/usr/bin/perl # IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html # Builds the xCAT database man pages from the descriptions that are contained # in Schema.pm. This script is run during the build of the perl-xCAT rpm, but # is not packaged in the binary form of that rpm. # We assume that this script is run in the perl-xCAT-2.0 dir, so everything is # done relative to that. use strict; use lib '.'; use xCAT::Schema; use xCAT::Table; use Pod::Man; use Pod::Html; my $poddir = 'pods'; my $mandir = 'share/man'; my $htmldir = 'share/doc'; my $cachedir = '/tmp'; my $poddir5 = 'pods/man5'; my $poddir7 = 'pods/man7'; if (system("mkdir -p $poddir5")) { die "Error: could not create $poddir5.\n"; } if (system("mkdir -p $poddir7")) { die "Error: could not create $poddir7.\n"; } # Build the DB overview page. print "Building PODs pages for the database tables...\n"; writesummarypage("$poddir5/xcatdb.5.pod", xCAT::Table->getDescriptions(), \%{xCAT::Schema::defspec}); # Build the pad man page for each object definition my $defspecref = \%{xCAT::Schema::defspec}; foreach my $defkey (keys %$defspecref) { my $def = $defspecref->{$defkey}; my $attrs = $def->{'attrs'}; writedefmanpage("$poddir7/$defkey.7.pod", $defkey, $attrs); } # Build the pod man page for each table. my $tabspecref = \%xCAT::Schema::tabspec; foreach my $tablekey (keys %$tabspecref) { my $table = $tabspecref->{$tablekey}; my $summary = $table->{table_desc}; my $colorder = $table->{cols}; my $descriptions = $table->{descriptions}; writepodmanpage("$poddir5/$tablekey.5.pod", $tablekey, $summary, $colorder, $descriptions); } my @pods = getPodList($poddir); #foreach (@pods) { print "$_\n"; } exit; # Build the man page for each pod. #mkdir($mandir) or die "Error: could not create $mandir.\n"; print "Converting PODs to man pages...\n"; foreach my $podfile (@pods) { my $manfile = $podfile; $manfile =~ s/^$poddir/$mandir/; # change the beginning of the path $manfile =~ s/\.pod$//; # change the ending my $mdir = $manfile; $mdir =~ s|/[^/]*$||; # get rid of the basename part if (system("mkdir -p $mdir")) { die "Error: could not create $mdir.\n"; } my ($section) = $podfile =~ /\.(\d+)\.pod$/; convertpod2man($podfile, $manfile, $section); } # Build the html page for each pod. #mkdir($htmldir) or die "Error: could not create $htmldir.\n"; print "Converting PODs to HTML pages...\n"; # have to clear the cache, because old entries can cause a problem unlink("$cachedir/pod2htmd.tmp", "$cachedir/pod2htmi.tmp"); foreach my $podfile (@pods) { my $htmlfile = $podfile; $htmlfile =~ s/^$poddir/$htmldir/; # change the beginning of the path $htmlfile =~ s/\.pod$/\.html/; # change the ending my $hdir = $htmlfile; $hdir =~ s|/[^/]*$||; # get rid of the basename part if (system("mkdir -p $hdir")) { die "Error: could not create $hdir.\n"; } convertpod2html($podfile, $htmlfile, $poddir, $htmldir); } exit; # Recursively get the list of pod man page files. sub getPodList { my $poddir = shift; my @files; # 1st get toplevel dir listing opendir(DIR, $poddir) or die "Error: could not read $poddir.\n"; my @topdir = grep !/^\./, readdir(DIR); # / close(DIR); # Now go thru each subdir (these are man1, man3, etc.) foreach my $mandir (@topdir) { opendir(DIR, "$poddir/$mandir") or die "Error: could not read $poddir/$mandir.\n"; my @dir = grep !/^\./, readdir(DIR); # / close(DIR); foreach my $file (@dir) { push @files, "$poddir/$mandir/$file"; } } return sort @files; } # Create the html page for one pod. sub convertpod2html { my ($podfile, $htmlfile, $poddir, $htmldir) = @_; #TODO: use --css= and --title= to make the pages look better pod2html($podfile, "--outfile=$htmlfile", "--podpath=man5:man7", "--podroot=$poddir", "--htmldir=$htmldir", "--recurse", "--cachedir=$cachedir", ); } # Create the man page for one pod. sub convertpod2man { my ($podfile, $manfile, $section) = @_; my $parser = Pod::Man->new(section => $section); $parser->parse_from_file($podfile, $manfile); } # Create the xcatdb man page that gives a summary description of each table. sub writesummarypage { my $file = shift; # relative path file name of the man page my $descriptions = shift; # a hash containing the description of each table my $defdescriptions = shift; # a hash containing the description of each object definition open(FILE, ">$file") or die "Error: could not open $file for writing.\n"; print FILE <<'EOS1'; =head1 NAME An overview of the xCAT database. =head1 DESCRIPTION The xCAT database contains user settings for the cluster and information gathered from the cluster. It consists of a series of tables, which are described below. To get more information about a particular table, run man for that table name. The tables can be manipulated directly using the B or B commands. They can be viewed using B or B. Alternatively, the xCAT database can be viewed and edited as logical objects, instead of flat tables. In this mode, xCAT takes care of which table each attribute should go in. To treat the database as logical object definitions, use the commands: lsdef, mkdef, chdef, rmdef. See Object Definitions below. xCAT allows the use of different database applications, depending on the needs of your cluster. The default database is SQLite, which is a daemonless, zero-config database. But you could instead choose to use something like postgresql for greater scalability and remote access in the hierarchical/service node case. To use a different database or a different location, create the file /etc/xcat/cfgloc. See the appropriate xCAT docuementation for the format of the file for the database you choose. The following example is for PostgreSQL: =over 4 =item SQLite:/var/xcat/cfgloc =item Pg:dbname=xcat;host=|| where mgmtnode is the hostname of the management node adapter on the cluster side, and the pgadminuserid and pgadminpasswd are the database admin and password. =back The xCAT database spans a number of tables, some with records associated with particular nodes (such as nodelist and nodehm) and others that do not have a direct relationship with any given node. The tables not associated with a given node are straightforward, the data is stored and retrieved as-is from the database without interpretation, and without any generic inheritance (though some calling code may implement inheritance for specific fields, for example nodehm.power inheriting from nodehm.mgt). The tables with records typically retrieved by node name have some extra features to enable a more template-based style to be used: Any group name can be used in lieu of a node name in the node field, and that record will then be taken to be applicable to any node in that group. If a field is requested for a specific node, and either a record doesn't exist specifically for that nodename or a record exists, but has no definition for the requested field, that node's groups are then used to search for records. If multiple records could apply from two different groups, the precedence is the order the groups are specified in the nodelist table for that node. This is nearly identical to most xCAT 1.x tab file conventions. This is useful in tables such as noderes, where typical configurations have exactly the same field values for large sets of nodes. xCAT 2 extends the above to be made useful where a field will vary for every node with a given tag, but in ways that would be trivial to describe. If a field is of the format /I/I/, it is taken to be a perl regular expression, to be performed on the nodename. For example, the bmc field of the ipmi table might be B for a record with node=ipmi to specify that the BMC hostname is derived by appending B<-bmc> to the end of the nodename of every node in the ipmi group. As an extension to the above, a regular expression extended with arithmetic operators is available, by using the format |I|I|. This behaves similarly to the above, but () enclosed parts in I are taken to signify arithmetic operations and substituted in. All operations are integer arithmetic, so 5/4 would come out as 1. The typical perl positional variables are available in such expressions. For example, if you have many blades in your cluster and their hostnames have a regular pattern of blade1, blade2, etc., and your BladeCenter management modules also have a hostname pattern of amm1, amm2, etc., then your B table could be expressed by the following single row: "blade","|\D+(\d+)|amm(($1-1)/14+1)|","|\D+(\d+)|(($1-1)%14+1)|",, Before you panic, let me explain each column: =over 4 =item B This is a group name. In this example, we are assuming that all of your blades belong to this group. Each time the xCAT software accesses the B table to get the management module and slot number of a specific blade (e.g. B), this row will match (because B is in the B group). Once this row is matched for B, then the processing described in the following items will take place. =item B<|\D+(\d+)|amm(($1-1)/14+1)|> This is a perl substitution pattern that will produce the value for the second column of the table (the management module hostname). The text B<\D+(\d+)> between the 1st two vertical bars is a regular expression that matches the node name that was searched for in this table (in this example B). The text that matches within the 1st set of parentheses is set to $1. (If there was a 2nd set of parentheses, it would be set to $2, and so on.) In our case, the \D+ matches the non-numeric part of the name (B) and the \d+ matches the numeric part (B<20>). So $1 is set to B<20>. The text B between the 2nd and 3rd vertical bars produces the string that should be used as the value for this column in a hypothetical row for blade20. Since $1 is set to 20, the expression B<($1-1)/14+1> equals 19/14 + 1, which equals 2. Therefore the whole string is B, which will be used as the hostname of the management module. =item B<|\D+(\d+)|(($1-1)%14+1)|> This item is similar to the one above. This substituion pattern will produce the value for the 3rd column (the BladeCenter chassis slot number for this blade). Because this row was the match for B, the parentheses within the 1st set of vertical bars will set $1 to 20. Since % means modulo division, the expression B<($1-1)%14+1> will evaluate to 6. =back See http://www.perl.com/doc/manual/html/pod/perlre.html for information on perl regular expressions. =head1 OBJECT DEFINITIONS Because it can get confusing what attributes need to go in what tables, the xCAT database can also be viewed and edited as logical objects, instead of flat tables. Use B, B, B, and B to create, change, list, and delete objects. When using these commands, the object attributes will be stored in the same tables, as if you edited the tables by hand. The only difference is that the object commands take care of knowing which tables all of the information should go in. To run man for any of the object definitions below, use section 7. For example: B The object types are: =over 2 EOS1 foreach my $def (sort keys %$defdescriptions) { print FILE "\n=item L<$def(7)|$def.7>\n"; } print FILE <<"EOS2"; =back =head1 TABLES To manipulate the tables directly, use B, B, B, B, B, B. To run man for any of the table descriptions below, use section 5. For example: B The tables are: =over 2 EOS2 foreach my $table (sort keys %$descriptions) { print FILE "\n=item L<$table(5)|$table.5>\n\n".$descriptions->{$table}."\n"; } print FILE <<"EOS3"; =back =head1 SEE ALSO B, B, B, B, B, B, B, B EOS3 close FILE; } # Create the man page for one object definition. sub writedefmanpage { my $file = shift; # relative path file name of the man page my $defname = shift; # name of object my $attrs = shift; # reference to the array of attributes # Go thru the attributes, collecting the descriptions # Note: this logic is loosely taken from DBobjectdefs.pm my %attrlist; # holds the attr name as the key, and the description & tables as value foreach my $this_attr (@$attrs) { my $attr = $this_attr->{attr_name}; my $desc = $this_attr->{description}; my ($table, $at) = split(/\./, $this_attr->{tabentry}); if (!defined($desc)) { # description key not there, so go to the corresponding # entry in tabspec to get the description my $schema = xCAT::Table->getTableSchema($table); $desc = $schema->{descriptions}->{$at}; } # Attr names can appear more than once, if they are in multiple tables. # We will keep track of that based on the table attribute, because that can be duplicated too if (!defined($attrlist{$attr})) { $attrlist{$attr}->{'tables'} = []; # initialize the array, so we can check it below } my $tableattr = "$table.$at"; if (!grep(/^$tableattr$/, @{$attrlist{$attr}->{'tables'}})) { # there can be multiple entries that refer to the same table attribute # if this is a new table attribute, then save the attr name and description push @{$attrlist{$attr}->{'tables'}}, $tableattr; push @{$attrlist{$attr}->{'descriptions'}}, $desc; } } open(FILE, ">$file") or die "Error: could not open $file for writing.\n"; print FILE <<"EOS1"; =head1 NAME B<$defname> - a logical object definition in the xCAT database. =head1 SYNOPSIS EOS1 print FILE "B<$defname Attributes:> I<" . join('>, I<',sort(keys(%attrlist))) . ">\n"; print FILE <<"EOS2"; =head1 DESCRIPTION Logical objects of this type are stored in the xCAT database in one or more tables. Use the following commands to manipulate the objects: B, B, B, and B. These commands will take care of knowing which tables the object attributes should be stored in. The attribute list below shows, in parentheses, what tables each attribute is stored in. =head1 $defname Attributes: =over 6 EOS2 foreach my $a (sort keys %attrlist) { my $d = join("\nor\n", @{$attrlist{$a}->{'descriptions'}}); $d =~ s/\n/\n\n/sg; # if there are newlines, double them so pod sees a blank line, otherwise pod will ignore them my $t = '(' . join(', ',@{$attrlist{$a}->{'tables'}}) . ')'; #print FILE "\nB<$a> - $d\n"; print FILE "\n=item B<$a> $t\n\n$d\n"; } print FILE <<"EOS3"; =back =head1 SEE ALSO B, B, B, B EOS3 close FILE; } # Create the man page for one table. sub writepodmanpage { my $file = shift; # relative path file name of the man page my $tablename = shift; # name of table my $summary = shift; # description of table my $colorder = shift; # the order in which the table attributes should be presented in my $descriptions = shift; # a hash containing the description of each attribute open(FILE, ">$file") or die "Error: could not open $file for writing.\n"; print FILE <<"EOS1"; =head1 NAME B<$tablename> - a table in the xCAT database. =head1 SYNOPSIS EOS1 print FILE "B<$tablename Attributes:> I<" . join('>, I<',@$colorder) . ">\n"; print FILE <<"EOS2"; =head1 DESCRIPTION $summary =head1 $tablename Attributes: =over 10 EOS2 foreach my $a (@$colorder) { my $d = $descriptions->{$a}; #$d =~ s/\n/\n\n/sg; # if there are newlines, double them so pod sees a blank line, otherwise pod will ignore them #print FILE "\nB<$a> - $d\n"; print FILE "\n=item B<$a>\n\n$d\n"; } print FILE <<"EOS3"; =back =head1 SEE ALSO B, B, B, B EOS3 close FILE; }