package FS::part_export::nas_wrapper; =head1 FS::part_export::nas_wrapper This is a meta-export that triggers other exports for FS::svc_broadband objects based on a set of configurable conditions. These conditions are defined by the following FS::router virtual fields: =over 4 =item nas_conf - Per-router meta-export configuration. See L. =back =head2 nas_conf Syntax export_name|routernum[,routernum]|[field,condition[,field,condition]][||...] =over 4 =item export_name - Name or exportnum of the export to be executed. In order to specify export options you must use the exportnum form. (ex. 'router' for FS::part_export::router). =item routernum - FS::router routernum corresponding to the desired FS::router for which this export will be run. =item field - FS::svc_broadband field (real or virtual). The following condition (regex) will be matched against the value of this field. =item condition - A regular expression to be match against the value of the previously listed FS::svc_broadband field. =back If multiple routernum's are specified, then the export will be triggered for each router listed. If multiple field/condition pairs are present, then the results of the matches will be and'd. Note that if a false match is found, the rest of the matches may not be checked. You can specify multiple export/router/condition sets by concatenating them with '||'. =cut use strict; use vars qw(@ISA %info $me $DEBUG); use FS::Record qw(qsearchs); use FS::part_export; use Tie::IxHash; use Data::Dumper qw(Dumper); @ISA = qw(FS::part_export); $me = '[' . __PACKAGE__ . ']'; $DEBUG = 0; %info = ( 'svc' => 'svc_broadband', 'desc' => 'A meta-export that triggers other svc_broadband exports.', 'options' => {}, 'notes' => '', ); sub rebless { shift; } sub _export_insert { my($self) = shift; $self->_export_command('insert', @_); } sub _export_delete { my($self) = shift; $self->_export_command('delete', @_); } sub _export_suspend { my($self) = shift; $self->_export_command('suspend', @_); } sub _export_unsuspend { my($self) = shift; $self->_export_command('unsuspend', @_); } sub _export_replace { my($self) = shift; $self->_export_command('replace', @_); } sub _export_command { my ( $self, $action, $svc_broadband) = (shift, shift, shift); my ($new, $old); if ($action eq 'replace') { $new = $svc_broadband; $old = shift; } my $router = $svc_broadband->addr_block->router; return '' unless grep(/^nas_conf$/, $router->fields); my $nas_conf = $router->nas_conf; my $child_exports = &_parse_nas_conf($nas_conf); my $error = ''; my $queue_child_exports = {}; # Similar to FS::svc_Common::replace, calling insert, delete, and replace # exports where necessary depending on which conditions match. if ($action eq 'replace') { my @new_child_exports = (); my @old_child_exports = (); # Find all the matching "new" child exports. foreach my $child_export (@$child_exports) { my $match = &_test_child_export_conditions( $child_export->{'conditions'}, $new, ); if ($match) { push @new_child_exports, $child_export; } } # Find all the matching "old" child exports. foreach my $child_export (@$child_exports) { my $match = &_test_child_export_conditions( $child_export->{'conditions'}, $old, ); if ($match) { push @old_child_exports, $child_export; } } # Insert exports for new. push @{$queue_child_exports->{'insert'}}, ( map { my $new_child_export = $_; if (! grep { $new_child_export eq $_ } @old_child_exports) { $new_child_export->{'args'} = [ $new ]; $new_child_export; } else { (); } } @new_child_exports ); # Replace exports for new and old. push @{$queue_child_exports->{'replace'}}, ( map { my $new_child_export = $_; if (grep { $new_child_export eq $_ } @old_child_exports) { $new_child_export->{'args'} = [ $new, $old ]; $new_child_export; } else { (); } } @new_child_exports ); # Delete exports for old. push @{$queue_child_exports->{'delete'}}, ( grep { my $old_child_export = $_; if (! grep { $old_child_export eq $_ } @new_child_exports) { $old_child_export->{'args'} = [ $old ]; $old_child_export; } else { (); } } @old_child_exports ); } else { foreach my $child_export (@$child_exports) { my $match = &_test_child_export_conditions( $child_export->{'conditions'}, $svc_broadband, ); if ($match) { $child_export->{'args'} = [ $svc_broadband ]; push @{$queue_child_exports->{$action}}, $child_export; } } } warn "[debug]$me Dispatching child exports... " . &Dumper($queue_child_exports) if $DEBUG; # Actually call the child exports now, with their preset action and arguments. foreach my $_action (keys(%$queue_child_exports)) { foreach my $_child_export (@{$queue_child_exports->{$_action}}) { $error = &_dispatch_child_export( $_child_export, $_action, @{$_child_export->{'args'}}, @_, ); # Bail if there's an error queueing one of the exports. # This will all get rolled-back. return $error if $error; } } return ''; } sub _parse_nas_conf { my $nas_conf = shift; my @child_exports = (); foreach my $cond_set ($nas_conf =~ m/(.*?[^\\])(?:\|\||$)/g) { warn "[debug]$me cond_set is '$cond_set'" if $DEBUG; my @args = $cond_set =~ m/(.*?[^\\])(?:\||$)/g; my %child_export = ( 'export' => $args[0], 'routernum' => [ split(/,\s*/, $args[1]) ], 'conditions' => { @args[2..$#args] }, ); warn "[debug]$me " . Dumper(\%child_export) if $DEBUG; push @child_exports, { %child_export }; } return \@child_exports; } sub _dispatch_child_export { my ($child_export, $action, @args) = (shift, shift, @_); my $child_export_name = $child_export->{'export'}; my @routernums = @{$child_export->{'routernum'}}; my $error = ''; # And the real hack begins... my $child_part_export; if ($child_export_name =~ /^(\d+)$/) { my $exportnum = $1; $child_part_export = qsearchs('part_export', { exportnum => $exportnum }); unless ($child_part_export) { return "No such FS::part_export with exportnum '$exportnum'"; } $child_export_name = $child_part_export->exporttype; } else { $child_part_export = new FS::part_export { 'exporttype' => $child_export_name, 'machine' => 'bogus', }; } warn "[debug]$me running export '$child_export_name' for routernum(s) '" . join(',', @routernums) . "'" if $DEBUG; my $cmd_method = "_export_$action"; foreach my $routernum (@routernums) { $error ||= $child_part_export->$cmd_method( @args, 'routernum' => $routernum, ); last if $error; } warn "[debug]$me export '$child_export_name' returned '$error'" if $DEBUG; return $error; } sub _test_child_export_conditions { my ($conditions, $svc_broadband) = (shift, shift); my $match = 1; foreach my $cond_field (keys %$conditions) { my $cond_regex = $conditions->{$cond_field}; warn "[debug]$me Condition: $cond_field =~ /$cond_regex/" if $DEBUG; unless ($svc_broadband->get($cond_field) =~ /$cond_regex/) { $match = 0; last; } } return $match; } 1;