source: LATMOS-Accounts/lib/LATMOS/Accounts/Synchro.pm @ 819

Last change on this file since 819 was 819, checked in by nanardon, 14 years ago
  • set LA_UPDATED env. var. when running post script: indicate if data were updated, the script can skip useless work so
  • take into account script error code if any, then assume there were an error
  • Property svn:keywords set to Id Rev
File size: 9.0 KB
Line 
1package LATMOS::Accounts::Synchro;
2
3use 5.010000;
4use strict;
5use warnings;
6use base qw(Config::IniFiles);
7use LATMOS::Accounts::Bases;
8use LATMOS::Accounts::Log;
9use LATMOS::Accounts::Utils qw(exec_command);
10use Fcntl qw(:flock);
11
12=head1 NAME
13
14LATMOS::Accounts::Synchro - Perl extension for blah blah blah
15
16=head1 SYNOPSIS
17
18  use LATMOS::Accounts;
19  blah blah blah
20
21=head1 DESCRIPTION
22
23Stub documentation for LATMOS::Accounts, created by h2xs. It looks like the
24author of the extension was negligent enough to leave the stub
25unedited.
26
27Blah blah blah.
28
29=head1 FUNCTIONS
30
31=cut
32
33our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
34
35=head2 new($from, $to, %options)
36
37Create a new synchronisation where $from and $to are LATMOS::Accounts::Base
38based objects. $to can be an array ref of objects.
39
40=cut
41
42sub new {
43    my ($class, $from, $to, %options) = @_;
44
45    my $state_file = $options{state_dir}
46        ? $options{state_dir} . '/synchronisation.ini'
47        : undef;
48    if ($state_file && ! -w $state_file) {
49        # don't exists, we have to create it
50        open(my $handle, '>', $state_file) or do {
51            la_log(LA_ERR, "Cannot open status file %s", $state_file);
52            return;
53        };
54        print $handle "[_default_]\n";
55        close($handle);
56    }
57
58    my $self = Config::IniFiles->new(
59        $state_file
60        ? (-file => $state_file)
61        : (),
62    );
63
64    if ($state_file && !$self->GetFileName) {
65        $self->SetFileName($state_file);
66    }
67   
68    $self->{from} = $from or do {
69        la_log(LA_ERR, "No database source");
70        return;
71    };
72    $self->{options} = { %options };
73
74    # allow ref and array ref of, eg
75    # to = $foo and $to = [ $foo, $bar ]
76    foreach (ref($to) eq 'ARRAY' ? @{ $to || []} : ($to)) {
77        push(@{$self->{to}}, $_);
78    }
79    bless($self, $class)
80}
81
82sub name {
83    $_[0]->{options}{name}
84}
85
86sub from {
87    my ($self) = @_;
88    return $self->{from}
89}
90
91sub to {
92    my ($self) = @_;
93    return @{$self->{to} || []};
94}
95
96=head2 load_dest
97
98Try to loaded all base, return the count of filtrered base which cannot
99be loaded
100
101=cut
102
103sub load_dest {
104    my ($self) = @_;
105    my @loaded;
106    my $unloaded = 0;
107    foreach ($self->to) {
108        if($_->load) {
109            push(@loaded, $_);
110        } else {
111            $unloaded++;
112            warn "Cannot load $_";
113        }
114    }
115    $self->{to} = \@loaded;
116    return $unloaded;
117}
118
119sub enter_synch_mode {
120    my ($self) = @_;
121    $self->from->load or return;
122    # if any cannot be loaded, return,
123    # TODO we need a way to force if some still can be sync
124    $self->load_dest and return;
125    my %state = ();
126    $state{$self->from->label} = $self->from->wexported(0);
127    foreach ($self->to) {
128        $state{$_->label} = $_->wexported(1);
129    }
130    la_log(LA_DEBUG, "Entering synch mode, old state: %s", join(', ', map {
131            "$_ => $state{$_}" } sort keys %state));
132    %state
133}
134
135sub leave_synch_mode {
136    my ($self, %state) = @_;
137    la_log(LA_DEBUG, "Leaving synch mode");
138    $self->from->wexported($state{$self->from->label});
139    foreach ($self->to) {
140        $_->commit;
141        $_->wexported($state{$_->label});
142    }
143}
144
145sub lock {
146    my ($self) = @_;
147
148    $self->{lock}{handle} and return 1;
149    la_log(LA_DEBUG, "Trying to lock (pid $$)");
150    if ($self->{options}{state_dir}) {
151        my $lockfile = $self->{options}{state_dir} . '/synclock';
152        open(my $handle, '>>', $lockfile) or return;
153        flock($handle, LOCK_EX);
154        $self->{lock}{handle} = $handle;
155        $self->{lock}{filename} = $lockfile;
156        la_log(LA_DEBUG, "lock done (pid $$)");
157        return 1;
158    } else { return 1 }
159}
160
161sub unlock {
162    my ($self) = @_;
163    if (my $handle = $self->{lock}{handle}) {
164        close($handle);
165        delete($self->{lock}{handle});
166        unlink($self->{lock}{filename});
167        delete($self->{lock}{filename});
168        return 1;
169    }
170    return;
171}
172
173sub sync_object {
174    my ($self, $otype, $uid, %options) = @_;
175
176    $self->lock or return;
177
178    my %state = $self->enter_synch_mode;
179   
180    my $res = $self->_sync_object($otype, $uid, %options);
181
182    $self->leave_synch_mode(%state);
183
184    $self->unlock;
185
186    $res;
187}
188
189sub _sync_object {
190    my ($self, $otype, $uid, %options) = @_;
191    foreach ($self->to) {
192        my $res = $_->sync_object_from($self->from, $otype, $uid, %options);
193        if (defined $res) {
194            la_log(LA_NOTICE, $_->label . " $uid ($otype) $res") if ($res);
195            return 1;
196        } else {
197            la_log(LA_ERR, "error synching $uid ($otype) to " . $_->label);
198            return;
199        }
200    }
201}
202
203sub process {
204    my ($self, %options) = @_;
205
206    $self->lock or return;
207
208    my %state = $self->enter_synch_mode;
209
210    # tracking current base revision:
211    $self->{current_rev} = $self->from->current_rev;
212
213    my %desterror;
214    my %existings;
215    my $updated = 0;
216    foreach my $destbase ($self->to) {
217        my %objlist;
218        foreach my $otype ($self->from->list_supported_objects) {
219            $destbase->is_supported_object($otype) or next;
220
221            $existings{$otype} ||= { map { $_ => 1 }
222                $self->from->list_objects($otype) };
223
224            # deleting non existing object in dest:
225            foreach ($destbase->list_objects($otype)) {
226                if(!$existings{$otype}{$_}) {
227                    if (my $res = $destbase->sync_object_from($self->from,
228                            $otype, $_, %options)) {
229                        la_log(LA_NOTICE, "%s::%s::%s => %s %s",
230                            $self->from->label, $otype, $_, $destbase->label, $res,
231                        );
232                        $updated = 1;
233                    }
234                }
235            }
236
237            @{$objlist{$otype}} = $self->from->list_objects_from_rev(
238                $otype,
239                $self->val($self->from->label, $destbase->label, 0),
240            );
241        }
242        foreach my $pass (1, 0) {
243            foreach my $otype (
244                sort { $a eq 'user' ? 1 : -1 } # user in last because gidNumber needed
245                keys %objlist) {
246                next if (!$pass && !$destbase->delayed_fields($otype));
247                foreach (@{$objlist{$otype} || []}) {
248                    my $res = $destbase->sync_object_from($self->from, $otype, $_,
249                        %options, firstpass => $pass);
250                    if (defined $res) {
251                        if ($res) {
252                            la_log(LA_NOTICE, "%s::%s::%s => %s %s",
253                                $self->from->label, $otype, $_,
254                                $destbase->label, $res,
255                            );
256                            $updated = 1;
257                        }
258                    } else {
259                        la_log(LA_ERR, "Cannot synch %s::%s::%s => %s",
260                            $self->from->label, $otype, $_,
261                            $destbase->label,
262                        );
263                        $desterror{$destbase->label} = 1;
264                    }
265
266                }
267            }
268        }
269    }
270
271    $self->leave_synch_mode(%state);
272    my $res = $self->run_post_synchro(
273        {
274            UPDATED => $updated,
275        }
276    );
277    if ($res) {
278        foreach my $destbase ($self->to) {
279            # don't register checkpoint on error
280            if ($desterror{$destbase->label}) { next; }
281            $self->newval($self->from->label, $destbase->label, $self->{current_rev});
282        }
283
284        if(!($self->{options}{nocreate} ||
285                $self->{options}{test})) {
286            $self->write_status;
287        }
288    } else {
289        la_log(LA_ERROR, "Not updating status because post script failed");
290    }
291
292    $self->unlock;
293
294    1;
295}
296
297sub write_status {
298    my ($self) = @_;
299    if (my $file = $self->GetFileName) {
300        open(my $handle, '>', $file) or do {
301            la_log(LA_ERR, "Cannot open status file %s for writing: %s",
302                $file, $!);
303            return;
304        };
305        my $oldfh = select($handle);
306        $self->OutputConfig();
307        select($oldfh);
308        close($handle); 
309        return 1;
310    }
311
312    return 0;
313}
314
315sub run_post_synchro {
316    my ($self, $env) = @_;
317
318    $self->{options}{post} or return 1;
319
320    la_log(LA_INFO, "Running post synchro `%s'", $self->{options}{post});
321
322    exec_command($self->{options}{post}, $env);
323}
324
325
3261;
327
328__END__
329# Below is stub documentation for your module. You'd better edit it!
330
331=head1 SEE ALSO
332
333Mention other useful documentation such as the documentation of
334related modules or operating system documentation (such as man pages
335in UNIX), or any relevant external documentation such as RFCs or
336standards.
337
338If you have a mailing list set up for your module, mention it here.
339
340If you have a web site set up for your module, mention it here.
341
342=head1 AUTHOR
343
344Thauvin Olivier, E<lt>olivier.thauvin@latmosipsl.frE<gt>
345
346=head1 COPYRIGHT AND LICENSE
347
348Copyright (C) 2009 by Thauvin Olivier
349
350This library is free software; you can redistribute it and/or modify
351it under the same terms as Perl itself, either Perl version 5.10.0 or,
352at your option, any later version of Perl 5 you may have available.
353
354
355=cut
Note: See TracBrowser for help on using the repository browser.