### ### Copyright 2000-2007 University of Illinois Board of Trustees ### All rights reserved. ### ### TSM.pm - TSM module for psgconf ### ### Campus Information Technologies and Educational Services ### University of Illinois at Urbana-Champaign ### package PSGConf::Control::TSM; use strict; use POSIX; use PSGConf::Action::GenerateFile::dsm_opt; use PSGConf::Action::GenerateFile::dsm_sys; use PSGConf::Action::GenerateFile::tsm_inclexcl; use PSGConf::Action::GenerateFile::Literal; use PSGConf::Action::RestartDaemon; use PSGConf::Data::Boolean; use PSGConf::Data::Hash; use PSGConf::Data::List; use PSGConf::Data::Integer; use PSGConf::Data::String; use PSGConf::Control::Packages qw(_add_pkgs); ############################################################################### ### Constructor ############################################################################### sub new { my ($class, $psgconf) = @_; my ($self); $self = {}; bless($self, $class); ### So that _add_pkgs knows which directives to look at $self->{name} = 'tsm'; $self->{enable} = $self->{name} . '_enable'; $self->{packages} = $self->{name} . '_packages'; $psgconf->register_data( tsm_enable => PSGConf::Data::Boolean->new( value => 'false' ), tsm_config_dir => PSGConf::Data::String->new( 'value_abspath' => 1, value => '/usr/bin' ), tsm_packages => PSGConf::Data::List->new(), tsm_df_command => PSGConf::Data::String->new( 'value_abspath' => 1, value => '/usr/local/bin/df' ), tsm_domain => PSGConf::Data::Hash->new( value_optional => 1, key_abspath => 1 ), tsm_domain_tree => PSGConf::Data::Hash->new( value_optional => 1, key_abspath => 1 ), tsm_domain_ignore => PSGConf::Data::Hash->new( value_optional => 1, key_abspath => 1 ), tsm_include => PSGConf::Data::Hash->new( value_type => 'HASH' ), tsm_exclude => PSGConf::Data::Hash->new( value_type => 'HASH' ), tsm_server => PSGConf::Data::String->new(), tsm_port => PSGConf::Data::Integer->new( value => 1500 ), tsm_options => PSGConf::Data::Hash->new() ); $psgconf->register_policy($self, tsm_check_server => '_policy_check_server', tsm_canonify_logfiles => '_policy_canonify_logfiles', tsm_modify_rc_vars => '_policy_modify_rc_vars', tsm_add_rc_scripts => '_policy_add_rc_scripts', tsm_add_domain_trees => '_policy_add_domain_trees', tsm_default_virtualmountp => '_policy_default_virtualmountp', tsm_default_inclexcl => '_policy_default_inclexcl', tsm_add_packages => '_add_pkgs' ); return $self; } ############################################################################### ### policy methods ############################################################################### sub _policy_modify_rc_vars { my ($self, $psgconf) = @_; $psgconf->data_obj('rc_vars')->insert( { 'linux_enable' => 'YES' } ) if ($psgconf->data_obj('platform')->match('freebsd') && $psgconf->data_obj('tsm_enable')->equals('true') ); } sub _policy_check_server { my ($self, $psgconf) = @_; $psgconf->data_obj('tsm_enable')->set('false') if (! defined $psgconf->data_obj('tsm_server')->get()); } ### create RC script sub _policy_add_rc_scripts { my ($self, $psgconf) = @_; $psgconf->data_obj('rc_scripts')->insert( { 'tsmsched' => { 'state' => 'enable' }} ) if ( $psgconf->data_obj('tsm_enable')->equals('true') ); } ### prepend log_dir to logfiles if they're not absolute paths sub _policy_canonify_logfiles { my ($self, $psgconf) = @_; my ($log_dir, $opts); return if ( $psgconf->data_obj('tsm_enable')->equals('false') ); $log_dir = $psgconf->data_obj('log_dir')->get(); $opts = $psgconf->data_obj('tsm_options')->get(); $opts->{Errorlogname} = $log_dir . '/' . $opts->{Errorlogname} if (exists($opts->{Errorlogname}) && substr($opts->{Errorlogname}, 0, 1) ne '/'); $opts->{Schedlogname} = $log_dir . '/' . $opts->{Schedlogname} if (exists($opts->{Schedlogname}) && substr($opts->{Schedlogname}, 0, 1) ne '/'); } ### calculate TSM domain sub _policy_add_domain_trees { my ($self, $psgconf) = @_; my ($domain, $domain_tree); return if ( $psgconf->data_obj('tsm_enable')->equals('false') ); $domain = $psgconf->data_obj('tsm_domain')->get(); $domain_tree = $psgconf->data_obj('tsm_domain_tree')->get(); map { $domain->{$_} = undef if (-d $_); } (keys %$domain_tree, _filesystems_beneath($psgconf->data_obj('tsm_df_command')->get(), keys %$domain_tree)); map { delete $domain->{$_}; } (keys %{$psgconf->data_obj('tsm_domain_ignore')->get()}); } ### calculate virtual mount points sub _policy_default_virtualmountp { my ($self, $psgconf) = @_; my ($opts); return if ( $psgconf->data_obj('tsm_enable')->equals('false') ); $opts = $psgconf->data_obj('tsm_options')->get(); $opts->{virtualmountp} = [ _not_a_filesystem($psgconf->data_obj('tsm_df_command')->get(), sort keys %{$psgconf->data_obj('tsm_domain')->get()}) ] if (! exists($opts->{virtualmountp})); } ### set inclexcl file if needed sub _policy_default_inclexcl { my ($self, $psgconf) = @_; my ($opts); return if ( $psgconf->data_obj('tsm_enable')->equals('false') ); $opts = $psgconf->data_obj('tsm_options')->get(); $opts->{INCLEXCL} = $psgconf->data_obj('tsm_config_dir')->get() . '/inex.list' if (! exists($opts->{INCLEXCL}) && ( $psgconf->data_obj('tsm_include')->count() || $psgconf->data_obj('tsm_exclude')->count() )); } ############################################################################### ### decide() method ############################################################################### sub decide { my ($self, $psgconf) = @_; my ($config_dir, $server, $opts, $include, $exclude); my ($label, $domain); return if ( $psgconf->data_obj('tsm_enable')->equals('false') ); $config_dir = $psgconf->data_obj('tsm_config_dir')->get(); $server = $psgconf->data_obj('tsm_server')->get(); $include = $psgconf->data_obj('tsm_include')->get(); $exclude = $psgconf->data_obj('tsm_exclude')->get(); $opts = $psgconf->data_obj('tsm_options')->get(); $domain = $psgconf->data_obj('tsm_domain')->get(); ### determine server label $label = $server; $label =~ s/\..*//; $psgconf->register_actions( PSGConf::Action::GenerateFile::dsm_sys->new( 'name' => $config_dir . '/dsm.sys', 'comment_str' => '***', 'description' => 'TSM configuration file', 'servers' => { $label => { server => $server, port => $psgconf->data_obj('tsm_port')->get(), opts => $opts } } ), PSGConf::Action::GenerateFile::dsm_opt->new( 'name' => $config_dir . '/dsm.opt', 'comment_str' => '***', 'description' => 'TSM options file', 'server' => $label, 'domain' => [ keys %$domain ] ) ); $psgconf->register_actions( PSGConf::Action::GenerateFile::tsm_inclexcl->new( 'name' => $config_dir . '/inex.list', 'comment_str' => undef, 'include' => $include, 'exclude' => $exclude ) ) if (%$include || %$exclude); ### ### Hacks to run TSM on FreeBSD systems ### $psgconf->register_actions( PSGConf::Action::GenerateFile::Literal->new( 'name' => '/compat/linux/etc/mtab', 'comment_str' => undef, 'content' => '/ / ext3 rw 0 0' ), PSGConf::Action::GenerateFile::Literal->new( 'name' => '/compat/linux/etc/ld.so.conf', 'comment_str' => undef, 'content' => join ("\n", ( '/lib', '/usr/lib', '/opt/tivoli/tsm/client/ba/bin', '/opt/tivoli/tsm/client/api/bin' )) ) ) if ( $psgconf->data_obj('platform')->match('freebsd') ); $psgconf->register_actions( PSGConf::Action::RestartDaemon->new( name => 'TSM', rcscript => $psgconf->data_obj('rc_scripts')->find('tsmsched')->{fullname}, use_restart => 'true', filename => [ $config_dir . '/dsm.sys', $config_dir . '/dsm.opt', $config_dir . '/inex.list' ] ) ); } ############################################################################### ### Utility functions ############################################################################### # Arguments: $_[0] ... - List of directories # Return value: List of filesystems beneath any listed directory sub _filesystems_beneath { my ($df_cmd, @dirs) = @_; my (@fs_tmp, %test_fs, %filesystems, %beneath, $line, $fs); ### Get the list of all (mounted) filesystems @fs_tmp = `$df_cmd` if ( defined $df_cmd ); foreach $line (@fs_tmp) { $fs = (split(/\s+/, $line))[5]; $filesystems{$fs} = 1 if (substr($fs, 0, 1) eq '/'); } map { $test_fs{$_} = 1; } @dirs; foreach $fs (keys %test_fs) { ### If the higher-up fs name fits into the lower one, the ### lower one is a child filesystem. map { $beneath{$_} = 1 if (($_ =~ m/^$fs/) && !exists($test_fs{$_})); } keys %filesystems; } return sort keys %beneath; } # Arguments: $_[0] ... - List of directories # Return value: Elements of argument list not representing filesystems sub _not_a_filesystem { my ($df_cmd, @dirs) = @_; my (@fs_tmp, %test_fs, %filesystems, %beneath, $line, $fs); ### Get the list of all (mounted) filesystems @fs_tmp = `$df_cmd` if ( defined $df_cmd ); foreach $line (@fs_tmp) { $fs = (split(/\s+/, $line))[5]; $filesystems{$fs} = 1 if (substr($fs, 0, 1) eq '/'); } map { $test_fs{$_} = 1; } @dirs; ### Anything left after this is not a filesystem map { delete $test_fs{$_}; } keys %filesystems; return sort keys %test_fs; } ############################################################################### ### documentation ############################################################################### 1; __END__ =head1 NAME PSGConf::Control::TSM - psgconf control class for TSM backup client =head1 SYNOPSIS In F: Control PSGConf::Control::TSM =head1 DESCRIPTION The B module provides a B control object for configuring the TSM backup client. It supports the following methods: =over 4 =item new() The constructor. Its parameter is a reference to the B object. It registers the following data objects: =over 4 =item I A B object indicating whether TSM should be configured. =item I A B object containing the absolute path to the TSM config directory. The default is F (yuck). =item I A B object containing the df(1) command to be used when generating a list of filesystems to be backed up. Note that this command must have the same output format as GNU df(1). The default is F. =item I A B object whose keys are a list of absolute paths to trees to be added to the TSM domain. For each tree, all filesystems under the tree will be added to the domain. If the tree itself is not a mount point, it will be added to the domain, and a C directive will be used for it. =item I A B object whose keys are a list of absolute paths to filesystems to be added to the TSM domain. Unlike I, these paths do not check the contents of the directory; they are assumed to be valid mount points and are added to the domain with no further processing. =item I A B object whose keys are a list of absolute paths to filesystems that should never be added to the TSM domain. This allows the exclusion of certain filesystems even if using I on a higher-level directory. =item I A B object that indicates what files should be included. The hash key is the type of file, which is used as an extension to the TSM C directive (i.e., a key of C will add to the C directive). The key can be set to C<-> to use no extension (i.e., for normal files). The hash value is a reference to a hash whose keys are the files to include. =item I A B object that indicates what files should be excluded. The hash key is the type of file, which is used as an extension to the TSM C directive (i.e., a key of C will add to the C directive). The key can be set to C<-> to use no extension (i.e., for normal files). The hash value is a reference to a hash whose keys are the files to exclude. =item I A B object containing options for the F file. =item I A B object containing the port number of the TSM server. The default is 1500. =item I A B object containing the packages to install. =item I A B object containing the hostname of the TSM server. =back The constructor also registers the following policy methods: =over 4 =item I If I is not set, unsets I. =item I If the C or C entries in the I data object are set to relative paths, prepends the value of the I data object (provided by B). =item I Creates an RC script to start the TSM scheduler at boot time. This is done using the I data object, which is provided by the B module. =item I For each entry in I, adds the entry and all filesystems mounted beneath it to I. Then, for each entry in I, deletes the corresponding entries in I. =item I If the I data object does not contain an entry for the C option, set it to a list of entries in I that do not correspond to mounted filesystems. =item I If the I or I objects are set and the I object does not contain an entry for the C option, set it to point to the file F in the directory specified by the I object. =back =item decide() If I is set, registers the following action objects: =over 4 =item * Registers a B object to create IF. =item * Registers a B object to create IF. =item * If the I or I objects are set, registers a B object to create IF. =back =back =head1 BUGS There is no support for multiple TSM servers. The scheduler is not restarted if it's in the middle of running a backup, which means that it's possible for config changes to never be noticed. The module runs an external df(1) program in order to determine mountpoints. It shouldn't need to invoke an external program to do this. =head1 SEE ALSO L L L L L L L L L L L L L L L =cut