Subversion Repositories DevTools

Rev

Rev 5404 | Rev 6098 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1038 dpurdie 1
#! /usr/bin/perl
2
########################################################################
3
# Copyright (C) 2011 Vix-ERG Limited, All rights reserved
4
#
5
# Module name   : blatDaemon.pl
6
# Module type   :
7
# Compiler(s)   : Perl
8
# Environment(s):
9
#
10
# Description   :
11
#
12
# Usage         :   ARGV[0] - Path to config file for this instance
13
#
14
#......................................................................#
15
 
16
require 5.008_002;
17
use strict;
18
use warnings;
19
use Getopt::Long;
20
use File::Basename;
1040 dpurdie 21
use Data::Dumper;
22
use File::Spec::Functions;
1038 dpurdie 23
use POSIX ":sys_wait_h";
24
use File::Temp qw/tempfile/;
25
 
26
use FindBin;                                    # Determine the current directory
27
use lib "$FindBin::Bin/lib";                    # Allow local libraries
28
 
29
use Utils;
30
use StdLogger;                                  # Log to sdtout
31
use Logger;                                     # Log to file
32
 
33
#
34
#   Database interface
35
#   Pinched from jats and modified so that this software is not dependent on JATS
36
#
37
use IO::Handle;
38
use JatsRmApi;
39
use DBI;
40
 
41
#
42
#   Globals
43
#
44
my $logger = StdLogger->new();                  # Stdout logger. Only during config
45
my $name = basename( $ARGV[0]);
46
   $name =~ s~.conf$~~;
47
my $now = 0;
5398 dpurdie 48
my $startTime = 0;
3515 dpurdie 49
my $tar = 'tar';
50
my $gzip = 'gzip';
1038 dpurdie 51
my $tagDirTime = 0;
52
my $lastDirScan = 0;
53
my $lastReleaseScan = 0;
1040 dpurdie 54
my $releaseScanMode = 0;
55
my $lastTagListScan = 0;
1038 dpurdie 56
my $transferred;
57
my $mtimeConfig = 0;
58
my $conf;
59
my $extraPkgs;
1040 dpurdie 60
my $excludePkgs;
61
my %releaseData;
3515 dpurdie 62
my $comError = 0;
5398 dpurdie 63
my $yday = -1;
1038 dpurdie 64
 
65
#
5398 dpurdie 66
#   Contain statisics maintained while operating
67
#       Can be dumped with a kill -USR2
68
#       List here for documentation
69
#  
70
 
71
my %statistics = (
72
    SeqNum => 0,                        # Bumped when $statistics are dumped
73
    timeStamp => 0,                     # DateTime when statistics are dumped
74
    upTime => 0,                        # Seconds since program start
75
    Cycle => 0,                         # Major process loop counter
76
    phase => 'Init',                    # Current phase of operation
77
    state => 'OK',                      # Nagios state
78
                                        # 
79
                                        # The following are reset each day
80
    dayStart => 0,                      # DateTime when daily data was reset
81
    txCount => 0,                       # Packages Transferred
82
    delCount => 0,                      # Packages marked for deletion
83
    staleTags => 0,                     # Stale Tags
84
    linkErrors => 0,                    # Transfer errors
85
                                        # 
86
                                        # Per Cycle Data - Calculated each processing Cycle
5399 dpurdie 87
    total    => 0,                      # Packages to be synced
5398 dpurdie 88
    delete   => 0,                      # Packages to delete
89
    excluded => 0,                      # Packages excluded    
90
    filtered => 0,                      # Packages filtered out
91
    missing  => 0,                      # Packages missing
92
    transfer => 0,                      # Packages to transfer
93
    writable => 0,                      # Packages still writable - thus not transferred
94
    tagCount => 0,                      # Packages tagged to be transferred
5404 dpurdie 95
                                        #
96
                                        # Expected from the Target 
97
#   Target.Hostname => '',              # Target Hostname
98
#   Target.avail    => 0,               # Information from 'df' 1Kblocks 
99
#   Target.pcent    => 0,
100
#   Target.size     => 0,
101
#   Target.used     => 0,
102
#   Target.iavail   => 0,               # Inode information from 'df'
103
#   Target.ipcent   => 0,
104
#   Target.isize    => 0,
105
#   Target.iused    => 0,
106
#   Target.Total    => 0,               # Number of Package Versions in the archive
107
#   Target.Damaged  => 0,               # Number that are damaged
108
#   Target.Delete   => 0,               # Number marked for future deletion
109
#   Target.Missing  => 0,               # Number missing
5398 dpurdie 110
);
111
 
1038 dpurdie 112
#
5398 dpurdie 113
#   Describe configuration parameters
114
#
1038 dpurdie 115
my %cdata = (
116
    '.ignore'         => {'pkg\.(.+)' => 'pkgs' },
1048 dpurdie 117
    'piddir'          => {'mandatory' => 1      , 'fmt' => 'dir'},
118
    'sleep'           => {'default'   => 5      , 'fmt' => 'period'},
119
    'dpkg_archive'    => {'mandatory' => 1      , 'fmt' => 'dir'},
120
    'logfile'         => {'mandatory' => 1      , 'fmt' => 'vfile'},
121
    'logfile.size'    => {'default'   => '1M'   , 'fmt' => 'size'},
122
    'logfile.count'   => {'default'   => 9      , 'fmt' => 'int'},
123
    'verbose'         => {'default'   => 0      , 'fmt' => 'int'},
124
    'user'            => {'mandatory' => 1      , 'fmt' => 'text'},
125
    'hostname'        => {'mandatory' => 1      , 'fmt' => 'text'},
3847 dpurdie 126
    'sshport'         => {'default'   => 0      , 'fmt' => 'int'},
1048 dpurdie 127
    'identity'        => {'mandatory' => 1      , 'fmt' => 'file'},
128
    'bindir'          => {'mandatory' => 1      , 'fmt' => 'text'},
129
    'tagdir'          => {'mandatory' => 1      , 'fmt' => 'dir'},
130
    'forcedirscan'    => {'default'   => 100    , 'fmt' => 'period'},
131
    'tagage'          => {'default'   => '10m'  , 'fmt' => 'period'},
132
    'tagListUpdate'   => {'default'   => '1h'   , 'fmt' => 'period'},
133
    'synctime'        => {'default'   => '2h'   , 'fmt' => 'period'},
134
    'syncretry'       => {'default'   => '5m'   , 'fmt' => 'period'},
135
    'allProjects'     => {'default'   => 0      , 'fmt' => 'bool'},
4456 dpurdie 136
    'allArchive'      => {'default'   => 0      , 'fmt' => 'bool'},
1048 dpurdie 137
    'project'         => {'mandatory' => 0      , 'fmt' => 'int_list'},
138
    'release'         => {'mandatory' => 0      , 'fmt' => 'int_list'},
139
    'writewindow'     => {'default'   => '3h'   , 'fmt' => 'period'},
140
    'maxpackages'     => {'default'   => 5      , 'fmt' => 'int'},
141
    'deletePackages'  => {'default'   => 0      , 'fmt' => 'bool'},
142
    'deleteImmediate' => {'default'   => 0      , 'fmt' => 'bool'},
143
    'deleteAge'       => {'default'   => 0      , 'fmt' => 'period'},
144
    'packageFilter'   => {'default'   => undef  , 'fmt' => 'text'},
145
    'active'          => {'default'   => 1      , 'fmt' => 'bool'},
1038 dpurdie 146
);
147
 
148
 
149
#
150
#   Read in the configuration
151
#       Set up a logger
152
#       Write a pidfile - thats not used
153
readConfig();
154
Utils::writepid($conf);
155
$logger->logmsg("Starting...");
5404 dpurdie 156
readStatistics();
1038 dpurdie 157
sighandlers($conf);
5398 dpurdie 158
$startTime = time();
1038 dpurdie 159
 
160
#
161
#   Main processing loop
162
#   Will exit when terminated by parent
163
#
3847 dpurdie 164
while (1 )
1038 dpurdie 165
{
166
    $logger->verbose3("Processing");
5398 dpurdie 167
    $statistics{Cycle}++;
1038 dpurdie 168
    $now = time();
1040 dpurdie 169
 
1038 dpurdie 170
    $transferred = {};
5398 dpurdie 171
    $statistics{phase} = 'ReadConfig';
1038 dpurdie 172
    readConfig();
1048 dpurdie 173
    if ( $conf->{'active'} )
174
    {
5398 dpurdie 175
        $statistics{phase} = 'ProcessReleaseList';
1048 dpurdie 176
        processReleaseList();
5398 dpurdie 177
        $statistics{phase} = 'processTags';
1048 dpurdie 178
        processTags();
5398 dpurdie 179
        $statistics{phase} = 'maintainTagList';
1048 dpurdie 180
        maintainTagList();
181
    }
1040 dpurdie 182
    %releaseData = ();
183
 
5398 dpurdie 184
    $statistics{phase} = 'Sleep';
1038 dpurdie 185
    sleep( $conf->{'sleep'} );
1050 dpurdie 186
 
187
    #
188
    #   Reap any and all dead children
189
    #
5398 dpurdie 190
    $statistics{phase} = 'Reaping';
1050 dpurdie 191
    my $kid;
192
    do {
193
        $kid = waitpid(-1, WNOHANG);
194
    } while ( $kid > 0 );
3847 dpurdie 195
 
196
    #   If my PID file ceases to be, then exit the daemon
197
    #   Used to force daemon to restart
198
    #
199
    unless ( -f $conf->{'pidfile'} )
200
    {
201
        $logger->logmsg("Terminate. Pid file removed");
202
        last;
203
    }
1038 dpurdie 204
}
5398 dpurdie 205
$statistics{phase} = 'Terminated';
1038 dpurdie 206
$logger->logmsg("Child End");
207
exit 0;
208
 
209
#-------------------------------------------------------------------------------
210
# Function        : readConfig
211
#
212
# Description     : Re read the config file if it modification time has changed
213
#
214
# Inputs          : Nothing
215
#
1289 dpurdie 216
# Returns         : 0       - Config not read
217
#                   1       - Config read
218
#                             Config file has changed
1038 dpurdie 219
#
220
sub readConfig
221
{
222
    my ($mtime) = Utils::mtime($ARGV[0]);
1289 dpurdie 223
    my $rv = 0;
224
 
1038 dpurdie 225
    if ( $mtimeConfig != $mtime )
226
    {
227
        $logger->logmsg("Reading config file: $ARGV[0]");
228
        $mtimeConfig = $mtime;
229
        my $errors;
230
        ($conf, $errors) = Utils::readconf ( $ARGV[0], \%cdata );
231
        if ( scalar @{$errors} > 0 )
232
        {
233
            warn "$_\n" foreach (@{$errors});
234
            die ("Config contained errors\n");
235
        }
236
 
237
        #
238
        #   Reset some information
239
        #   Create a new logger
240
        #
241
        $logger = Logger->new($conf);
242
        $conf->{logger} = $logger;
243
        $conf->{'pidfile'} = $conf->{'piddir'} . '/' . $name . '.pid';
244
        $logger->verbose("Log Levl: $conf->{verbose}");
245
 
246
        #
5398 dpurdie 247
        #   Setup statistics filename
248
        $conf->{'statsfile'} = $conf->{'piddir'} . '/' . $name . '.stats';
249
        $conf->{'statsfiletmp'} = $conf->{'piddir'} . '/' . $name . '.stats.tmp';
250
 
251
        #
1038 dpurdie 252
        #   Extract extra package config
253
        #
254
        $extraPkgs = {};
1040 dpurdie 255
        $excludePkgs = {};
1038 dpurdie 256
        while (my($key, $data) = each ( %{$conf->{pkgs}} ))
257
        {
3846 dpurdie 258
            if ( $data eq 'EXCLUDE' ) {
1040 dpurdie 259
                $excludePkgs->{$key} = 1;
260
                $logger->verbose("Exclude Pkg: $key");
3846 dpurdie 261
 
262
            } elsif ( $data eq 'ALL' ) {
263
                foreach my $pver (getPackageVersions($key))
264
                {
265
                    $extraPkgs->{$key}{$pver} = 1;
266
                    $logger->verbose("Extra Pkg: $key -> $pver");
267
                }
268
            } else {
1040 dpurdie 269
                $extraPkgs->{$key}{$data} = 1;
270
                $logger->verbose("Extra Pkg: $key -> $data");
271
            }
1038 dpurdie 272
        }
1046 dpurdie 273
 
1048 dpurdie 274
        $logger->verbose("Filter Packages: " . $conf->{'packageFilter'})
275
            if ( defined $conf->{'packageFilter'} );
276
 
3847 dpurdie 277
        $logger->warn("Non standard ssh port: " . $conf->{'sshport'})
278
            if ( $conf->{'sshport'} );
279
 
1048 dpurdie 280
        $logger->warn("Transfer session configured as not active")
281
            unless ( $conf->{'active'} );
282
 
283
        $logger->warn("Transfer all projects packages")
1050 dpurdie 284
            if ( $conf->{'allProjects'} );
4456 dpurdie 285
        $logger->warn("Transfer entire package archive")
286
            if ( $conf->{'allArchive'} );
1048 dpurdie 287
 
4456 dpurdie 288
 
1046 dpurdie 289
        #
290
        #   When config is read force some actions
291
        #       - Force tagList to be created
1050 dpurdie 292
        #       - Force release scan
1048 dpurdie 293
        $lastTagListScan = 0;
1050 dpurdie 294
        $lastReleaseScan = 0;
1289 dpurdie 295
        $rv = 1;
1038 dpurdie 296
    }
1289 dpurdie 297
    return $rv;
1038 dpurdie 298
}
299
 
300
 
301
#-------------------------------------------------------------------------------
302
# Function        : processReleaseList
303
#
304
# Description     : Process the release list
305
#                       Determine if its time to process release list
306
#                       Determine release list
307
#                       Determine release content
308
#                       Determine new items
309
#
310
# Inputs          : None
311
#
312
# Returns         : Nothing
313
#
314
sub processReleaseList
315
{
316
    #
1044 dpurdie 317
    #   Is Release List Processing active
318
    #   Can configure blat to disable release sync
319
    #   This will then allow 'new' packages to be sent
320
    #
321
    if ( $conf->{maxpackages} == 0 || $conf->{'synctime'} <= 0)
322
    {
323
        $logger->verbose2("processReleaseList disabled");
324
        return;
325
    }
326
 
327
    #
1038 dpurdie 328
    #   Time to perform the scan
329
    #   Will do at startup and every time period there after
330
    #
1040 dpurdie 331
    my $wtime = $releaseScanMode ? $conf->{'syncretry'} : $conf->{'synctime'};
332
    return unless ( $now > ($lastReleaseScan + $wtime ));
1038 dpurdie 333
    $logger->verbose("processReleaseList");
334
    $lastReleaseScan = $now;
1040 dpurdie 335
    $releaseScanMode = 1;                                   # Assume error
1038 dpurdie 336
 
337
    #
338
    #   Get list of packages from Remote site
339
    #   Invoke a program on the remote site and parse the results
340
    #
341
    #   Returned data looks like:
1042 dpurdie 342
    #   1141792602 GMT(Wed Mar  8 04:36:42 2006) [DL] ishieldmodules/11.5.0.cots
1038 dpurdie 343
    #   
344
    #
345
    my $remotePkgList;
5404 dpurdie 346
    my $remoteData;
1038 dpurdie 347
    my $ph;
1040 dpurdie 348
    my $tgt_cmd = "$conf->{'bindir'}/get_plist.pl";
3515 dpurdie 349
    my $ssh_cmd = sshCmd($tgt_cmd);
1038 dpurdie 350
 
351
    $logger->verbose2("processReleaseList:ssh_cmd:$ssh_cmd");
352
    open ($ph, "$ssh_cmd |");
353
    while ( <$ph> )
354
    {
355
        chomp;
5404 dpurdie 356
        if ($_ =~ m~^Metadata\s+~)
1038 dpurdie 357
        {
5404 dpurdie 358
            parsePkgMetaData($_, \%{$remoteData})
1038 dpurdie 359
        }
360
        else
361
        {
5404 dpurdie 362
            if ( parsePkgList($_, \%{$remotePkgList} ) )
363
            {
364
                $logger->verbose2("processReleaseList:Data: $_");
365
            }
366
            else
367
            {
368
                $logger->warn("processReleaseList:Bad Data: $_");
369
            }
1038 dpurdie 370
        }
371
    }
372
    close ($ph);
373
    $logger->verbose("processReleaseList:End: $?");
3515 dpurdie 374
    LogTxError ($?);
1038 dpurdie 375
    if ( $? != 0 )
376
    {
377
        $logger->warn("Cannot retrieve package list: $?");
5398 dpurdie 378
        $statistics{state} = 'No Remote Package List';
1038 dpurdie 379
        return;
380
    }
3515 dpurdie 381
 
1042 dpurdie 382
#Utils::DebugDumpData ("remotePkgList", $remotePkgList);
4456 dpurdie 383
 
1038 dpurdie 384
    #
385
    #   Determine the set of packages in the releases to be transferred
4456 dpurdie 386
    # 
387
    my $pkgList;
388
    if ( $conf->{'allArchive'} )
1040 dpurdie 389
    {
4456 dpurdie 390
        #   Examine entire archive
391
        #
392
        $pkgList = getArchiveList();
1040 dpurdie 393
    }
4456 dpurdie 394
    else
395
    {
396
        #   Examine Releases
397
        #
398
        my @rlist = getReleaseList();
399
        unless ( @rlist )
400
        {
401
            $logger->verbose2("No Releases to Process");
5398 dpurdie 402
            $statistics{state} = 'No Releases found';
4456 dpurdie 403
            return;
404
        }
405
        $pkgList = getPkgList(@rlist);
406
    }
1038 dpurdie 407
 
408
    #
409
    #   Append extra packages
410
    #   These are packages that are specifically named by the user
411
    #
412
    #   Note: If there are symbolic links, then the target of the
413
    #         link is treated used as the package name
414
    #
415
    #         Symlink MUST be within the same directory
3846 dpurdie 416
    #           Used to transfer jats2_current
1038 dpurdie 417
    #
418
    my $pkgLink;
419
    while ( (my ($pname, $pvers)) = each %{$extraPkgs} ) {
420
        while ( (my ($pver, $pdata) ) = each %{$pvers} ) {
421
 
1040 dpurdie 422
            my $epath = catfile( $conf->{'dpkg_archive'} , $pname, $pver );
1038 dpurdie 423
            if ( -l $epath )
424
            {
425
                my $lver = readlink( $epath );
426
                if ( ! defined $lver )
427
                {
3846 dpurdie 428
                    $logger->warn("Can't resolve symlink: $pname, $pver");
1038 dpurdie 429
                    next;
430
                }
431
 
432
                if ( $lver =~ m ~/~ )
433
                {
434
                    $logger->warn("Won't resolve symlink: $pname, $pver, $lver");
435
                    next;
436
                }
437
                $pkgLink->{$pname}{$pver} = $lver;
438
                $pdata = $pver;
439
                $pver = $lver;
440
            }
441
 
442
            $logger->verbose2("Add extra package: $pname, $pver, $pdata");
443
            $pkgList->{$pname}{$pver} = $pdata;
444
        }
445
    }
446
 
1040 dpurdie 447
    #
448
    #   If there are no packages to process, then assume that this is an error
449
    #   condition. Retry the operation soon.
450
    #
451
    unless ( keys %{$pkgList} )
452
    {
453
 
454
        $logger->verbose2("No packages to process");
5398 dpurdie 455
        $statistics{state} = 'No Packages found';
1040 dpurdie 456
        return;
457
    }
458
 
1038 dpurdie 459
#    while ( (my ($pname, $pvers)) = each %{$pkgList} )
460
#    {
461
#        while ( (my ($pver, $ptime) ) = each %{$pvers} )
462
#        {
463
#            print "L-- $pname, $pver, $ptime \n";
464
#
465
#        }
466
#    }
467
 
468
    #
1040 dpurdie 469
    #   Delete Excess Packages
1038 dpurdie 470
    #       Packages not required on the target
1042 dpurdie 471
    #           KLUDGE: Don't delete links to packages
472
    #           Don't delete packages marked for deletion
1038 dpurdie 473
    #
474
    my $excessPkgList;
1048 dpurdie 475
    my $excessPkgListCount = 0;
1038 dpurdie 476
    if ( $conf->{deletePackages} )
477
    {
478
        while ( (my ($pname, $pvers)) = each %{$remotePkgList} )
479
        {
480
            while ( (my ($pver, $pdata) ) = each %{$pvers} )
481
            {
482
                if ( !exists $pkgList->{$pname}{$pver} )
483
                {
484
                    if ( exists $pkgLink->{$pname}{$pver} )
485
                    {
486
                        $logger->verbose2("Keep Excess package-link: ${pname}/${pver}");
487
                        next;
488
                    }
1040 dpurdie 489
 
490
                    if ( exists $excludePkgs->{$pname} )
491
                    {
492
                        $logger->verbose2("Keep Excluded package: ${pname}");
493
                        next;
494
                    }
495
 
1042 dpurdie 496
                    if ( exists $pdata->{deleted} )
497
                    {
498
                        if ( $conf->{deleteAge} )
499
                        {
500
                            if ( $pdata->{deleted} <= $conf->{deleteAge} )
501
                            {
502
                                $logger->verbose2("Already marked for future age deletion: ${pname}/${pver}, $pdata->{deleted}");
503
                                next;
504
                            }
505
                            $pdata->{FORCEDELETE} = 1;
506
                        }
507
 
508
                        if ( !$conf->{deleteImmediate} )
509
                        {
510
                            $logger->verbose2("Already marked for deletion: ${pname}/${pver}");
511
                            next;
512
                        }
513
                    }
514
 
515
                    #
516
                    #   Force deletion
517
                    #       deleteImmediate mode
518
                    #       target is a broken link
519
                    #
520
                    $pdata->{FORCEDELETE} = 1
521
                        if ($conf->{deleteImmediate} || $pdata->{broken});
522
 
1038 dpurdie 523
                    $excessPkgList->{$pname}{$pver} = $pdata;
1048 dpurdie 524
                    $excessPkgListCount++;
1038 dpurdie 525
                    $logger->verbose("Excess package: ${pname}/${pver}");
526
                }
1050 dpurdie 527
#                else
528
#                {
529
#                        $logger->verbose3("Retain package: ${pname}/${pver}");
530
#                }
1038 dpurdie 531
            }
532
        }
533
    }
534
 
535
    #
536
    #   Process the remote list and the local list
537
    #   The remote time-stamp is the modification time of the packages descpkg file
538
    #
539
    #   Mark for transfer packages that
1040 dpurdie 540
    #       Are in the local set but not the remote set
1038 dpurdie 541
    #       Have a different time stamp
542
    #
543
    #   Ignore packages not in the local archive
544
    #   Ignore packages that don't have a descpkg
545
    #   Ignore packages that are writable - still being formed
546
    #
547
    my $needPkgList;
1048 dpurdie 548
    my $needPkgListCount = 0;
549
    my $filteredCount = 0;
550
    my $missingCount = 0;
551
    my $writableCount = 0;
552
    my $excludeCount = 0;
5404 dpurdie 553
    my $packageVersionCount = 0;
1038 dpurdie 554
    while ( (my ($pname, $pvers)) = each %{$pkgList} )
555
    {
1040 dpurdie 556
        #
557
        #   Ignore excluded packages
558
        #
1048 dpurdie 559
        if ( exists $excludePkgs->{$pname} )
560
        {
561
            $excludeCount++;
562
            next;
563
        }
1040 dpurdie 564
 
1048 dpurdie 565
        #
566
        #   Ignore packages that are filtered out
567
        #
568
        if ( defined $conf->{'packageFilter'} )
569
        {
570
            unless ( $pname =~ m~$conf->{'packageFilter'}~ )
571
            {
572
                $logger->verbose3("Filtering out: ${pname}");
573
                $filteredCount++;
574
                next;
575
            }
576
        }
577
 
1038 dpurdie 578
        while ( (my ($pver, $pdata) ) = each %{$pvers} )
579
        {
1042 dpurdie 580
            my $tmtime = $remotePkgList->{$pname}{$pver}{time} || 0;
5404 dpurdie 581
            $packageVersionCount++;
1038 dpurdie 582
 
583
            # Package is present in both list
1040 dpurdie 584
            my ($mtime, $mode) = Utils::mtime( catfile( $conf->{'dpkg_archive'} , $pname, $pver, 'descpkg' ));
1038 dpurdie 585
            if ( $mtime == 0 )
586
            {
587
                # PackageVersion not in local archive (at least the descpkg file is not)
588
                # Skip now - will pick it up later
589
                $logger->verbose("Package not in dpkg_archive: $pname, $pver");
1048 dpurdie 590
                $missingCount++;
1038 dpurdie 591
                next;
592
            }
593
 
594
            if ( $mode & 0222 )
595
            {
596
                # Descpkg file is writable
597
                # Package may be in the process of being created
1048 dpurdie 598
                # If the package has been writable for a long time, then
1038 dpurdie 599
                # consider for transfer
600
                my $age = $now - $mtime;
601
                if ( $age < ($conf->{'writewindow '} || 600) )
602
                {
603
                    $logger->verbose("Package is writable: $pname, $pver, ", $now - $mtime);
1048 dpurdie 604
                    $writableCount++;
1038 dpurdie 605
                    next;
606
                }
607
            }
608
 
609
            if ( $mtime != $tmtime )
610
            {
611
                # Package not present on target, or timestamps differ
612
                $logger->verbose("Package Needs to be transferred: $pname, $pver, $mtime, $tmtime");
613
                $needPkgList->{$pname}{$pver} = $pdata;
1048 dpurdie 614
                $needPkgListCount++;
1038 dpurdie 615
                next;
616
            }
617
        }
618
    }
619
 
1048 dpurdie 620
 
1038 dpurdie 621
    #
622
    #   Debug output only
623
    #   Display what we need to transfer
624
    #
625
    if ( $conf->{verbose} > 2 )
626
    {
627
        while ( (my ($pname, $pvers)) = each %{$needPkgList} )
628
        {
629
            while ( (my ($pver, $pdata) ) = each %{$pvers} )
630
            {
631
                $logger->verbose("Need to transfer: $pname, $pver, $pdata");
632
            }
633
        }
634
    }
1048 dpurdie 635
    if ( $conf->{verbose}  )
636
    {
637
        $logger->verbose("Packages to transfer: $needPkgListCount");
638
        $logger->verbose("Packages to delete: $excessPkgListCount");
639
        $logger->verbose("Packages filtered out: $filteredCount");
640
        $logger->verbose("Packages missing: $missingCount");
641
        $logger->verbose("Packages still writable: $writableCount");
642
        $logger->verbose("Packages excluded: $excludeCount");
643
    }
1038 dpurdie 644
 
645
    #
5398 dpurdie 646
    #   Update stats
647
    #   At this point we are looking pretty good
648
    #   
649
    $statistics{state} = 'OK';
5404 dpurdie 650
    $statistics{total} = $packageVersionCount;
5398 dpurdie 651
    $statistics{transfer} = $needPkgListCount;
652
    $statistics{delete} = $excessPkgListCount;
653
    $statistics{filtered} = $filteredCount;
654
    $statistics{missing} = $missingCount;
655
    $statistics{writable} = $writableCount;
656
    $statistics{excluded} = $excludeCount;
657
 
658
    #
1038 dpurdie 659
    #   Time to do the real work
660
    #   Transfer packages and delete excess packages
661
    #   Note: Perform the transfers first
662
    #         Limit the number of packages processed in one pass
663
    #
664
    my $txcount = $conf->{maxpackages};
665
 
666
    #
667
    #   Transfer packages that we have identified
668
    #
669
    send_pkgs:
670
    while ( (my ($pname, $pvers)) = each %{$needPkgList} )
671
    {
672
        while ( (my ($pver, $pdata) ) = each %{$pvers} )
673
        {
674
            if ( --$txcount <= 0 )
675
            {
1050 dpurdie 676
                $logger->warn("Max transfer count exceeded: $needPkgListCount transfer remaining");
1038 dpurdie 677
                $lastReleaseScan = 0;
678
                last send_pkgs;
679
            }
1289 dpurdie 680
 
681
            if ( readConfig() )
682
            {
683
                $logger->warn("Config file changed");
684
                $lastReleaseScan = 0;
685
                $txcount = 0;
686
                last send_pkgs;
687
            }
688
 
1044 dpurdie 689
            transferPackage ($pname, $pver, $pdata);
1048 dpurdie 690
            $needPkgListCount--;
1038 dpurdie 691
        }
692
    }
693
 
694
    #
695
    #   Delete packages that have been identified as excess
696
    #
697
    delete_pkgs:
698
    while ( (my ($pname, $pvers)) = each %{$excessPkgList} )
699
    {
700
        while ( (my ($pver, $pdata) ) = each %{$pvers} )
701
        {
702
            if ( --$txcount <= 0 )
703
            {
1050 dpurdie 704
                $logger->warn("Max transfer count exceeded: $excessPkgListCount deletion remaining");
1038 dpurdie 705
                $lastReleaseScan = 0;
706
                last delete_pkgs;
707
            }
1289 dpurdie 708
 
709
            if ( readConfig() )
710
            {
711
                $logger->warn("Config file changed");
712
                $lastReleaseScan = 0;
713
                $txcount = 0;
714
                last send_pkgs;
715
            }
716
 
1044 dpurdie 717
            deletePackage ($pname, $pver, $pdata);
1048 dpurdie 718
            $excessPkgListCount--;
1038 dpurdie 719
        }
720
    }
721
 
722
    #
723
    #   Send package list to the target
724
    #
725
    sendPackageList ($pkgList);
1040 dpurdie 726
 
727
    #
728
    #   On a successful transfer
729
    #       Force tag processing
730
    #       Set scan Mode to normal
731
    #
732
    $tagDirTime = 0;
733
    $releaseScanMode = 0;
1038 dpurdie 734
}
735
 
736
#-------------------------------------------------------------------------------
737
# Function        : sendPackageList
738
#
739
# Description     : Transfer package list to the target
740
#
741
# Inputs          : $pkgList            - Ref to hash of package names and versions
742
#
1040 dpurdie 743
# Returns         : Nothing
744
#                   Don't really care about any errors from this process
745
#                   Its not essential
1038 dpurdie 746
#
747
sub sendPackageList
748
{
749
    my ($pkgList) = @_;
750
    my ($fh, $filename) = tempfile( "/tmp/blat.$$.XXXX", SUFFIX => '.txt');
751
    $logger->verbose("sendPackageList:TmpFile: $filename");
752
 
753
    #
754
    #   Create a temp file with data
755
    #
756
    foreach my $pname ( sort keys %{$pkgList} )
757
    {
758
        foreach my $pver ( sort keys %{$pkgList->{$pname}} )
759
        {
760
            print $fh "$pname/$pver\n";
761
        }
762
    }
763
    close $fh;
764
 
765
    #
766
    #   Transfer to target
767
    #   Create the process pipe to transfer the file
768
    #   gzip the file and pipe the result through a ssh session to the target machine
3515 dpurdie 769
    #   gzip -c filename |  ssh  ... "./receive_file filename"
1038 dpurdie 770
    #
771
    my $ph;
3515 dpurdie 772
    my $gzip_cmd = "$gzip --no-name -c \"$filename\"";
1040 dpurdie 773
    my $tgt_cmd = "$conf->{'bindir'}/receive_file \"ArchiveList\"";
3515 dpurdie 774
    my $ssh_cmd = sshCmd($tgt_cmd);
1038 dpurdie 775
 
776
    $logger->verbose2("sendPackageList:gzip_cmd:$gzip_cmd");
777
    $logger->verbose2("sendPackageList:tgt_cmd:$tgt_cmd");
778
    $logger->verbose2("sendPackageList:ssh_cmd:$ssh_cmd");
779
 
780
    open ($ph, "$gzip_cmd | $ssh_cmd |");
781
    while ( <$ph> )
782
    {
783
        chomp;
784
        $logger->verbose2("sendPackageList:Data: $_");
785
    }
786
    close ($ph);
3515 dpurdie 787
    unlink $filename;
1038 dpurdie 788
    $logger->verbose("sendPackageList:End: $?");
3515 dpurdie 789
    LogTxError ($?);
1038 dpurdie 790
}
791
 
792
 
793
#-------------------------------------------------------------------------------
794
# Function        : getPkgList
795
#
796
# Description     : Determine a set of package versions within the list
797
#                   of provided releases
798
#
799
# Inputs          : @rlist              - A list of releases to examine
800
#
801
# Returns         : Ref to a hask of package versions
802
#
803
sub getPkgList
804
{
805
    my %pdata;
806
    my $RM_DB;
807
    connectRM(\$RM_DB);
808
    $logger->verbose("getPkgList");
809
 
810
    #
811
    #   Determine the releases that are in this project
812
    #   Build up an sql query
813
    #
814
    my @m_rlist;
815
    push @m_rlist,"rc.RTAG_ID=$_" foreach ( @_ );
816
    my $m_rlist = join ' OR ', @m_rlist;
1048 dpurdie 817
    my $m_sqlstr = "SELECT DISTINCT pv.PV_ID, pkg.PKG_NAME, pv.PKG_VERSION, pv.IS_DEPLOYABLE" .
1038 dpurdie 818
                    " FROM RELEASE_MANAGER.RELEASE_CONTENT rc, RELEASE_MANAGER.PACKAGE_VERSIONS pv, RELEASE_MANAGER.PACKAGES pkg" .
819
                    " WHERE ( $m_rlist ) AND rc.PV_ID = pv.PV_ID AND pv.PKG_ID = pkg.PKG_ID" .
820
                    " ORDER by PKG_NAME DESC";
821
    $logger->verbose3("getPkgList:Sql:$m_sqlstr");
822
 
823
    my $sth = $RM_DB->prepare($m_sqlstr);
824
    if ( defined($sth) )
825
    {
826
        if ( $sth->execute( ) )
827
        {
828
            if ( $sth->rows )
829
            {
830
                while (my @row = $sth->fetchrow_array )
831
                {
832
                    $logger->verbose2("getPkgList:Data:@row");
833
                    $pdata{$row[1]}{$row[2]} = 1;
834
                }
835
            }
836
            $sth->finish();
837
        }
838
    }
839
    else
840
    {
841
        $logger->warn("getPkgList: SQL Prepare failure");
842
    }
1050 dpurdie 843
 
844
   disconnectRM(\$RM_DB);
1038 dpurdie 845
   return \%pdata;
846
}
847
 
848
 
849
#-------------------------------------------------------------------------------
850
# Function        : getReleaseList
851
#
852
# Description     : Determine the list of releases to be proccessed
1040 dpurdie 853
#                   From:
854
#                       Convert projects to a list of releases
855
#                       Configured list of releases
1038 dpurdie 856
#
857
# Inputs          : None
858
#
859
# Returns         : A list of releases to be processed
860
#
861
sub getReleaseList
862
{
863
    my $RM_DB;
864
    my %rlist;
1048 dpurdie 865
    my $m_sqlstr;
1038 dpurdie 866
    $logger->verbose("getReleaseList");
867
 
868
    #
1040 dpurdie 869
    #   Cache data
870
    #   Only for one cycle of the main loop
871
    #
872
    if ( exists $releaseData{getReleaseList} )
873
    {
874
        $logger->verbose3("getReleaseList:Cache hit");
875
        return @{$releaseData{getReleaseList}};
876
    }
877
 
878
    #
1048 dpurdie 879
    #   All projects
1038 dpurdie 880
    #
1048 dpurdie 881
    if ( $conf->{'allProjects'} )
1038 dpurdie 882
    {
1048 dpurdie 883
        $m_sqlstr = "SELECT rt.RTAG_ID" .
884
                    " FROM RELEASE_MANAGER.RELEASE_TAGS rt" .
1050 dpurdie 885
                    " WHERE rt.OFFICIAL != 'A'";
886
                    #" AND rt.OFFICIAL != 'Y'";
1048 dpurdie 887
    }
888
    else
889
    {
1038 dpurdie 890
        #
1048 dpurdie 891
        #   Convert list of projects into a list of releases
1038 dpurdie 892
        #
1048 dpurdie 893
        my @plist = split /[,\s]+/, $conf->{'project'} || '';
894
        if ( @plist )
895
        {
896
            #
897
            #   Determine the releases that are in this project
898
            #   Build up an sql query
899
            #
900
            my @m_plist;
901
            push @m_plist,"PROJ_ID=$_" foreach ( @plist );
902
            my $m_plist = join ' OR ', @m_plist;
903
            $m_sqlstr = "SELECT rt.RTAG_ID" .
904
                        " FROM RELEASE_MANAGER.RELEASE_TAGS rt" .
5300 dpurdie 905
                        " WHERE ( $m_plist ) AND rt.OFFICIAL != 'A'";
906
                        #" AND rt.OFFICIAL != 'Y'";
1048 dpurdie 907
        }
908
    }
1038 dpurdie 909
 
1048 dpurdie 910
    if ( defined $m_sqlstr )
911
    {
1038 dpurdie 912
        $logger->verbose3("getReleaseList:Sql:$m_sqlstr");
1048 dpurdie 913
        connectRM(\$RM_DB);
1038 dpurdie 914
        my $sth = $RM_DB->prepare($m_sqlstr);
915
        if ( defined($sth) )
916
        {
917
            if ( $sth->execute( ) )
918
            {
919
                if ( $sth->rows )
920
                {
921
                    while (my @row = $sth->fetchrow_array )
922
                    {
923
                        $logger->verbose2("getReleaseList:Data:@row");
924
                        $rlist{$row[0]} = 1;
925
                    }
926
                }
927
                $sth->finish();
928
            }
929
        }
930
        else
931
        {
932
            $logger->warn("getReleaseList: SQL Prepare failure");
933
        }
1050 dpurdie 934
        disconnectRM(\$RM_DB);
1038 dpurdie 935
    }
936
 
937
    #
938
    #   Add in the user specified list of releases
939
    #
940
    my @rlist = split /[,\s]+/, $conf->{'release'} || '';
941
    $rlist{$_} = 1 foreach(@rlist);
942
 
943
    #
944
    #   Sort for pretty display only
945
    #
1040 dpurdie 946
    @{$releaseData{getReleaseList}} = sort {$a <=> $b} keys %rlist;
947
 
948
    return @{$releaseData{getReleaseList}};
1038 dpurdie 949
}
950
 
1040 dpurdie 951
#-------------------------------------------------------------------------------
3846 dpurdie 952
# Function        : getPackageVersions
953
#
954
# Description     : Get the list of package-versions available in the package
955
#                   store.
956
#
957
# Inputs          : pkgName             - The package name
958
#
959
# Returns         : Array of versions
960
#
961
sub getPackageVersions
962
{
963
    my ($pkgName) = @_;
964
    my @versionList;
965
 
966
    my $pkgDir = catfile($conf->{'dpkg_archive'} , $pkgName );
967
    my $dh;
968
 
969
    unless (opendir($dh, $pkgDir))
970
    {
971
        $logger->warn ("Can't opendir $pkgDir: $!");
972
        return @versionList;
973
    }
974
 
975
    #
976
    #   Process each entry
977
    #   Ignore those that start with a .
978
    #
979
    while (my $version = readdir($dh) )
980
    {
981
        next if ( $version =~ m~^\.~ );
982
        my $file = catfile($pkgDir, $version);
983
 
984
        next unless ( -d $file );
985
 
986
        push @versionList, $version;
987
        $logger->verbose3("getPackageVersions: $pkgName, $version");
988
    }
989
    closedir $dh;
990
    return @versionList;
991
}
992
 
993
#-------------------------------------------------------------------------------
4456 dpurdie 994
# Function        : getArchiveList 
995
#
996
# Description     : Get the entire set of package versions in the archive
997
#
998
# Inputs          : 
999
#
1000
# Returns         : Ref to a hask of package versions
1001
#
1002
sub getArchiveList
1003
{
1004
    my $pkgDir = $conf->{'dpkg_archive'};
1005
    my %archiveList;
1006
    my $dh;
1007
    my @pkgList;
1008
 
1009
    unless (opendir($dh, $pkgDir))
1010
    {
1011
        $logger->warn ("Can't opendir $pkgDir: $!");
1012
        return \%archiveList;
1013
    }
1014
 
1015
    #
1016
    #   Process each entry
1017
    #   Ignore those that start with a .
1018
    #   Ignore files
1019
    #
1020
    while (my $pkgName = readdir($dh) )
1021
    {
1022
        next if ( $pkgName =~ m~^\.~ );
1023
        my $file = catfile($pkgDir, $pkgName);
1024
 
1025
        next unless ( -d $file );
1026
        $logger->verbose3("getArchiveList: $pkgName");
1027
        push @pkgList, $pkgName;
1028
    }
1029
    closedir $dh;
1030
 
1031
    #   Now get the package versions
1032
    #       Sort for pretty display
1033
    foreach my $pname (sort @pkgList)
1034
    {
1035
        foreach my $pver (getPackageVersions($pname))
1036
        {
1037
            $archiveList{$pname}{$pver} = 1;
1038
        }
1039
    }
1040
 
1041
    return \%archiveList;
1042
}
1043
 
1044
#-------------------------------------------------------------------------------
1040 dpurdie 1045
# Function        : maintainTagList
1046
#
1047
# Description     : Maintain a data structure for the maintenance of the
1048
#                   tags directory
1049
#
1050
# Inputs          : None
1051
#
1052
# Returns         : Nothing
1053
#
1054
sub maintainTagList
1055
{
1056
    #
1057
    #   Time to perform the scan
1058
    #   Will do at startup and every time period there after
1059
    #
1060
    return unless ( $now > ($lastTagListScan + $conf->{tagListUpdate} ));
1061
    $logger->verbose("maintainTagList");
1062
    $lastTagListScan = $now;
1038 dpurdie 1063
 
1040 dpurdie 1064
    #
1065
    #   Get list of things
1066
    #
1067
    my %config;
4457 dpurdie 1068
    if ($conf->{'allArchive'} )
1069
    {
1070
        $config{allArchive} = 1
1071
    }
1072
    elsif ($conf->{'allProjects'} )
1073
    {
1074
        $config{allProjects} = 1;
1075
    }
1076
    else
1077
    {
1078
        %{$config{projects}} = map { $_ => 1 } split (/[,\s]+/, $conf->{'project'} || '');
1079
        %{$config{releases}} = map { $_ => 1 } getReleaseList();
1080
    }
1040 dpurdie 1081
 
1082
    #
1083
    #   Save data
1084
    #
1085
    my $dump =  Data::Dumper->new([\%config], [qw(*config)]);
1086
#print $dump->Dump;
1087
#$dump->Reset;
1088
 
1089
    #
1090
    #   Save config data
1091
    #
1092
    my $conf_file = catfile( $conf->{'tagdir'},'.config' );
1093
    $logger->verbose3("maintainTagList: Writting $conf_file");
1094
 
1095
    my $fh;
1096
    open ( $fh, '>', $conf_file ) or $logger->err("Can't create $conf_file: $!");
1097
    print $fh $dump->Dump;
1098
    close $fh;
1099
}
1100
 
1101
 
1038 dpurdie 1102
#-------------------------------------------------------------------------------
1103
# Function        : processTags
1104
#
1105
# Description     : Process tags and send marked package versions to the target
1106
#                       Determine if new tags are present
1107
#                       Process each tag
1108
#
1109
# Inputs          : None
1110
#
1111
# Returns         : Nothing
1112
#
1113
sub processTags
1114
{
1115
    #
1116
    #   Determine if new tags are present by examining the time
1117
    #   that the directory was last modified.
1118
    #
1119
    #   Allow for a forced scan to catch packages that did not transfer
1120
    #   on the first attempt
1121
    #
5398 dpurdie 1122
    my $tagCount = 0;
1038 dpurdie 1123
    my ($mtime) = Utils::mtime($conf->{'tagdir'} );
1124
    if ( ($mtime > $tagDirTime) || ($now > ($lastDirScan + $conf->{'forcedirscan'})) )
1125
    {
1042 dpurdie 1126
        $logger->verbose2("processTags: ",$conf->{'tagdir'}, $mtime - $tagDirTime, $now - $lastDirScan);
1038 dpurdie 1127
        $tagDirTime = $mtime;
1128
        $lastDirScan = $now;
1129
 
1130
        my $dh;
1131
        unless (opendir($dh, $conf->{'tagdir'}))
1132
        {
1133
            $logger->warn ("can't opendir $conf->{'tagdir'}: $!");
1134
            return;
1135
        }
1136
 
1137
        #
1138
        #   Process each entry
1139
        #   Ignore those that start with a .
1140
        #
1141
        while (my $tag = readdir($dh) )
1142
        {
1143
            next if ( $tag =~ m~^\.~ );
1144
            my $file = "$conf->{'tagdir'}/$tag";
1042 dpurdie 1145
            $logger->verbose3("processTags: $file");
1146
 
1038 dpurdie 1147
            next unless ( -f $file );
1148
            next if ( $tag  eq 'ReleaseList' );
1149
 
1150
            if ( $tag =~ m~(.+)::(.+)~  )
1151
            {
1152
                my $package = $1;
1153
                my $version = $2;
5398 dpurdie 1154
                $tagCount++;
1038 dpurdie 1155
                if ( transferPackage( $package, $version ))
1156
                {
1157
                    unlink $file;
1158
                }
1159
                else
1160
                {
1161
                    my ($mtime) = Utils::mtime( $file );
1162
                    if ( $now - $mtime > $conf->{'tagage'} )
1163
                    {
1164
                        $logger->warn ("Delete unsatisfied tag: $tag");
1165
                        unlink $file;
5398 dpurdie 1166
                        $statistics{staleTags}++;
1038 dpurdie 1167
                    }
1168
                }
1169
            }
1170
        }
1171
        closedir $dh;
1172
    }
5398 dpurdie 1173
    $statistics{tagCount} = $tagCount;
1038 dpurdie 1174
}
1175
 
1176
#-------------------------------------------------------------------------------
1177
# Function        : transferPackage
1178
#
1179
# Description     : Transfer specified package to target system
1180
#
1181
# Inputs          : $pname          - Name of the package
1182
#                   $pver           - Package version
1183
#                   $plink          - (optional) Symlink in same package
1184
#
1185
# Returns         : true    - Package transferred
1186
#                   false   - Package not transferred
1187
#
1188
sub transferPackage
1189
{
1190
    my ($pname, $pver, $plink ) = @_;
1191
    my $rv = 0;
1192
    $logger->logmsg("transferPackage: @_");
1193
 
1194
    #
1040 dpurdie 1195
    #   Do not transfer excluded files
1196
    #
1197
    if ( exists $excludePkgs->{$pname} )
1198
    {
1048 dpurdie 1199
        $logger->warn("transferPackage: Excluded package not transferred: $pname, $pver");
1040 dpurdie 1200
        return 1;
1201
    }
1048 dpurdie 1202
 
1040 dpurdie 1203
    #
1048 dpurdie 1204
    #   Apply package filter
1205
    #
1206
    if ( defined $conf->{'packageFilter'} )
1207
    {
1208
        unless ( $pname =~ m~$conf->{'packageFilter'}~ )
1209
        {
1210
            $logger->warn("transferPackage: Filtered out package not transferred: $pname, $pver");
1211
            return 1;
1212
        }
1213
    }
1214
 
1215
    #
1038 dpurdie 1216
    #   plink of 1 is not a symlink
1217
    #
1218
    $plink = undef if ( defined($plink) && $plink eq '1' );
1219
 
1220
    #
1221
    #   If its been transferred in the current scan, then
1222
    #   indicate that all is well
1223
    #
1224
    if ( $transferred->{$pname}{$pver}  )
1225
    {
1226
        $logger->verbose("transferPackage: Already transferred");
1227
        return 1;
1228
    }
1229
 
1040 dpurdie 1230
    my $sfile = catfile( $conf->{'dpkg_archive'} , $pname, $pver );
1038 dpurdie 1231
    unless ( -d $sfile )
1232
    {
1233
        $logger->warn("transferPackage:Package not found: $pname, $pver");
1234
        return $rv;
1235
    }
1236
 
1237
    #
4456 dpurdie 1238
    #   Create the process pipe to transfer the package
1038 dpurdie 1239
    #   Tar the directory and pipe the result through a ssh session to
1240
    #   the target machine
3515 dpurdie 1241
    #   $tar -czf - -C "$dpkg/${pname}/${pver}" . |  ssh  ... "./receive_package pname pver"
1038 dpurdie 1242
    #
1243
    my $ph;
3515 dpurdie 1244
    my $tar_cmd = "$tar -czf - -C \"$sfile\" .";
1038 dpurdie 1245
    my $tgt_opts = defined($plink) ? "\"-L$plink\"" : '';
1040 dpurdie 1246
    my $tgt_cmd = "$conf->{'bindir'}/receive_package $tgt_opts \"$pname\" \"$pver\"";
3515 dpurdie 1247
    my $ssh_cmd = sshCmd($tgt_cmd);
1038 dpurdie 1248
 
1249
    $logger->verbose2("transferPackage:tar_cmd:$tar_cmd");
1250
    $logger->verbose2("transferPackage:tgt_cmd:$tgt_cmd");
1251
    $logger->verbose2("transferPackage:ssh_cmd:$ssh_cmd");
1252
 
1253
    open ($ph, "$tar_cmd | $ssh_cmd |");
1254
    while ( <$ph> )
1255
    {
1256
        chomp;
1257
        $logger->verbose2("transferPackage:Data: $_");
1258
    }
1259
    close ($ph);
1260
    $logger->verbose("transferPackage:End: $?");
1261
    if ( $? == 0 )
1262
    {
1263
        #
1264
        #   Mark has having been transferred in the current cycle
1265
        #
1266
        $transferred->{$pname}{$pver} = 1;
1267
        $rv = 1;
5398 dpurdie 1268
        $statistics{txCount}++;
1038 dpurdie 1269
    }
1270
    else
1271
    {
1272
        $logger->warn("transferPackage:Transfer Error: $pname, $pver, $?");
1273
    }
3515 dpurdie 1274
    LogTxError ($?);
1038 dpurdie 1275
    return $rv;
1276
}
1277
 
1278
#-------------------------------------------------------------------------------
1279
# Function        : deletePackage
1280
#
1281
# Description     : Delete specified package to target system
1282
#
1283
# Inputs          : $pname          - Name of the package
1284
#                   $pver           - Package version
1042 dpurdie 1285
#                   $pdata          - Hash of extra data
1038 dpurdie 1286
#
1287
# Returns         : true    - Package transferred
1288
#                   false   - Package not transferred
1289
#
1290
sub deletePackage
1291
{
1042 dpurdie 1292
    my ($pname, $pver, $pdata ) = @_;
1038 dpurdie 1293
    my $rv = 0;
1042 dpurdie 1294
    $logger->logmsg("deletePackage: $pname, $pver");
1038 dpurdie 1295
 
1296
    #
1297
    #   Create the process pipe to delete the package
3515 dpurdie 1298
    #   ssh  ... "./delete_package ${rx_opts} \"$pname\" \"$pver\""
1038 dpurdie 1299
    #
1300
    my $ph;
1042 dpurdie 1301
    my $flags = $pdata->{FORCEDELETE}  ? '' : ' -T';
1302
    my $tgt_cmd = "$conf->{'bindir'}/delete_package $flags \"$pname\" \"$pver\"";
3515 dpurdie 1303
    my $ssh_cmd = sshCmd($tgt_cmd);
1038 dpurdie 1304
 
1040 dpurdie 1305
    $logger->verbose2("deletePackage:tgt_cmd:$tgt_cmd");
1306
    $logger->verbose2("deletePackage:ssh_cmd:$ssh_cmd");
1038 dpurdie 1307
 
1308
    open ($ph, "$ssh_cmd |");
1309
    while ( <$ph> )
1310
    {
1311
        chomp;
1312
        $logger->verbose2("deletePackage:Data: $_");
1313
    }
1314
    close ($ph);
1315
    $logger->verbose("deletePackage:End: $?");
1316
    if ( $? == 0 )
1317
    {
1318
        $rv = 1;
5398 dpurdie 1319
        $statistics{delCount}++;
1038 dpurdie 1320
    }
1321
    else
1322
    {
1323
        $logger->warn("deletePackage:Error: $pname, $pver, $?");
1324
    }
3515 dpurdie 1325
    LogTxError ($?);
1038 dpurdie 1326
    return $rv;
1327
}
1328
 
3515 dpurdie 1329
#-------------------------------------------------------------------------------
1330
# Function        : sshCmd
1331
#
1332
# Description     : Generate a ssh based command
1333
#
1334
# Inputs          : Target command
1335
#
1336
# Returns         : An shh command string
1337
#
1338
sub sshCmd
1339
{
1340
    my ($tgt_cmd) = @_;
3847 dpurdie 1341
    my $sshPort = '';
1342
    $sshPort = "-p $conf->{'sshport'}"
1343
        if ($conf->{'sshport'});
1344
 
1345
    return "ssh -o \"BatchMode yes\" -i $conf->{'identity'} ${sshPort} $conf->{'user'}\@$conf->{'hostname'} \"$tgt_cmd\" 2>&1";
3515 dpurdie 1346
}
1038 dpurdie 1347
 
3515 dpurdie 1348
 
1038 dpurdie 1349
#-------------------------------------------------------------------------------
1042 dpurdie 1350
# Function        : parsePkgList
1351
#
1352
# Description     : Parse one line from a pkgList
1353
#                   Lines are multiple item="data" items
1354
#
1355
# Inputs          : $line                   - Line of data
1356
#                   $hashp                  - Ref to hash to populate
1357
#
1358
# Returns         : A hash of data items
1359
#
1360
sub parsePkgList
1361
{
1362
    my ($line, $hashp) = @_;
1363
    my $rv;
1364
 
1365
    while ( $line =~ m~\s*(.+?)="(.+?)"~ )
1366
    {
1367
        $rv->{$1} = $2;
1368
        $line = $';
1369
    }
1370
#Utils::DebugDumpData ("parsePkgList", $rv);
1371
 
1372
    my $pname = $rv->{pname};
1373
    my $pver =  $rv->{pver};
1374
    return undef unless ( $pname && $pver );
1375
 
1376
    delete $rv->{pname};
1377
    delete $rv->{pver};
1378
    delete $rv->{GMT};
1379
 
1380
    $hashp->{$pname}{$pver} = $rv;
1381
    return $hashp;
1382
}
1383
 
5398 dpurdie 1384
#-------------------------------------------------------------------------------
5404 dpurdie 1385
# Function        : parsePkgMetaData
1386
#
1387
# Description     : Parse one line of meta data from a pkgList
1388
#                   Lines are multiple item="data" items
1389
#
1390
# Inputs          : $line                   - Line of data
1391
#                   $hashp                  - Ref to hash to populate
1392
#
1393
# Returns         : Nothing
1394
#
1395
sub parsePkgMetaData
1396
{
1397
    my ($line, $hashp) = @_;
1398
 
1399
    if ( $line =~ m~\s+(.+?)="(.+?)"~ )
1400
    {
1401
        $hashp->{$1} = $2;
1402
        $statistics{'Target.' . $1} = $2;
1403
        $line = $';
1404
 
1405
        $logger->verbose2("parsePkgMetaData: $1 = $2");
1406
    }
1407
}
1408
 
1409
 
1410
#-------------------------------------------------------------------------------
5398 dpurdie 1411
# Function        : resetDailyStatistics 
1412
#
1413
# Description     : Called periodically to reset the daily statistics
1414
#
1415
# Inputs          : $time       - Current time
1416
#
1417
# Returns         : 
1418
#
1419
sub resetDailyStatistics
1420
{
1421
    my ($time) = @_;
1042 dpurdie 1422
 
5398 dpurdie 1423
    #
1424
    #   Detect a new day
1425
    #
1426
    my $today = (localtime($time))[7];
1427
    if ($yday != $today)
1428
    {
1429
        $yday = $today;
1430
        $logger->logmsg('Resetting daily statistics' );
1431
 
5404 dpurdie 1432
        # Note: Must match @recoverTags in readStatistics
5398 dpurdie 1433
        $statistics{dayStart} = $time;
1434
        $statistics{txCount} = 0;
1435
        $statistics{delCount} = 0;
1436
        $statistics{staleTags} = 0;
1437
        $statistics{linkErrors} = 0;
1438
    }
1439
}
1440
 
1042 dpurdie 1441
#-------------------------------------------------------------------------------
5404 dpurdie 1442
# Function        : readStatistics 
1443
#
1444
# Description     : Read in the last set of stats
1445
#                   Used after a restart to recover daily statistics
1446
#
1447
# Inputs          : 
1448
#
1449
# Returns         : 
1450
#
1451
sub readStatistics
1452
{
1453
    my @recoverTags = qw(dayStart txCount delCount staleTags linkErrors);
1454
 
1455
    if ($conf->{'statsfile'} and -f $conf->{'statsfile'})
1456
    {
1457
        if (open my $fh, $conf->{'statsfile'})
1458
        {
1459
            while (<$fh>)
1460
            {
1461
                m~(.*):(.*)~;
1462
                if ( grep( /^$1$/, @recoverTags ) ) 
1463
                {
1464
                    $statistics{$1} = $2;
1465
                    $logger->verbose("readStatistics $1, $2\n");
1466
                }
1467
            }
1468
            close $fh;
1469
            $yday = (localtime($statistics{dayStart}))[7];
1470
        }
1471
    }
1472
}
1473
 
1474
 
1475
#-------------------------------------------------------------------------------
5398 dpurdie 1476
# Function        : periodicStatistics 
1477
#
1478
# Description     : Called on a regular basis to write out statistics
1479
#                   Used to feed information into Nagios
1480
#                   
1481
#                   This function is called via an alarm and may be outside the normal
1482
#                   processing loop. Don't make assumptions on the value of $now
1483
#
1484
# Inputs          : 
1485
#
1486
# Returns         : 
1487
#
1488
sub periodicStatistics
1489
{
1490
    #
1491
    #   A few local stats
1492
    #
1493
    $statistics{SeqNum}++;
1494
    $statistics{timeStamp} = time();
1495
    $statistics{upTime} = $statistics{timeStamp} - $startTime;
1496
 
1497
    #   Reset daily accumulations - on first use each day
1498
    resetDailyStatistics($statistics{timeStamp});
1499
 
1500
    #
1501
    #   Write statistics to a file
1502
    #       Write to a tmp file, then rename.
1503
    #       Attempt to make the operation atomic - so that the file consumer
1504
    #       doesn't get a badly formed file.
1505
    #   
1506
    if ($conf->{'statsfiletmp'})
1507
    {
1508
        my $fh;
1509
        unless (open ($fh, '>', $conf->{'statsfiletmp'}))
1510
        {
1511
            $fh = undef;
1512
            $logger->warn("Cannot create temp stats file: $!");
1513
        }
1514
        else
1515
        {
5404 dpurdie 1516
            foreach my $key ( sort { lc($a) cmp lc($b) } keys %statistics)
5398 dpurdie 1517
            {
1518
                print $fh $key . ':' . $statistics{$key} . "\n";
1519
                $logger->verbose2('Statistics:'. $key . ':' . $statistics{$key});
1520
            }
1521
            close $fh;
1522
 
1523
            # Rename temp to real file
1524
            rename  $conf->{'statsfiletmp'},  $conf->{'statsfile'} ;
1525
        }
1526
    }
1527
}
1528
 
1529
#-------------------------------------------------------------------------------
1038 dpurdie 1530
# Function        : sighandlers
1531
#
1532
# Description     : Install signal handlers
1533
#
1534
# Inputs          : $conf           - System config
1535
#
1536
# Returns         : Nothing
1537
#
1538
sub sighandlers
1539
{
5398 dpurdie 1540
    my $conf = shift;
1541
    my $logger = $conf->{logger};
1038 dpurdie 1542
 
5398 dpurdie 1543
    $SIG{TERM} = sub {
1544
        # On shutdown
1545
        $logger->logmsg('Received SIGTERM. Shutting down....' );
1546
        unlink $conf->{'pidfile'} if (-f $conf->{'pidfile'});
1547
        exit 0;
1548
    };
1038 dpurdie 1549
 
5398 dpurdie 1550
    $SIG{HUP} = sub {
1551
        # On logrotate
1552
        $logger->logmsg('Received SIGHUP.');
1553
        $logger->rotatelog();
1554
    };
1038 dpurdie 1555
 
5398 dpurdie 1556
    $SIG{USR1} = sub {
1557
        # On Force Archive Sync
1558
        $logger->logmsg('Received SIGUSR1.');
1046 dpurdie 1559
        $lastReleaseScan = 0;
1560
        $lastTagListScan = 0;
5398 dpurdie 1561
    };
1038 dpurdie 1562
 
5398 dpurdie 1563
    alarm 60;
1564
    $SIG{ALRM} = sub {
1565
        # On Dump Statistics
1566
        $logger->verbose2('Received SIGUSR2.');
1567
        periodicStatistics();
1568
        alarm 60;
1569
    };
1570
 
1038 dpurdie 1571
    $SIG{__WARN__} = sub { $logger->warn("@_") };
1572
    $SIG{__DIE__} = sub { $logger->err("@_") };
1573
}
1574
 
1575
#-------------------------------------------------------------------------------
3515 dpurdie 1576
# Function        : LogTxError
1577
#
1578
# Description     : Detect restoration of communication and log such
1579
#                   Don't log failures as the user will do that
1580
#
1581
# Inputs          : $state                  - 0 - All is well
1582
#                                           !0  - Error
1583
#
1584
# Returns         : Nothing
1585
#
1586
sub LogTxError
1587
{
1588
    my ($state) = $@;
1589
    if ( $state )
1590
    {
5398 dpurdie 1591
        $statistics{linkErrors}++ unless($comError);
3515 dpurdie 1592
        $comError++;
5398 dpurdie 1593
        $statistics{state} = 'No Communication';
3515 dpurdie 1594
    }
1595
    elsif ( $comError )
1596
    {
1597
        $logger->warn("Communication Restored");
1598
        $comError = 0;
5398 dpurdie 1599
        $statistics{state} = 'OK';
3515 dpurdie 1600
    }
1601
}
1602
 
1603
 
1604
#-------------------------------------------------------------------------------
1038 dpurdie 1605
# Function        : Error, Verbose, Warning
1606
#
1607
# Description     : Support for JatsRmApi
1608
#
1609
# Inputs          : Message
1610
#
1611
# Returns         : Nothing
1612
#
1613
sub Error
1614
{
1615
    $logger->err("@_");
1616
}
1617
 
1618
sub Verbose
1619
{
1042 dpurdie 1620
    $logger->verbose2("@_");
1038 dpurdie 1621
}
1622
 
1623
sub Warning
1624
{
1625
    $logger->warn("@_");
1626
}
1627
 
1628
 
1629
 
1630