Subversion Repositories DevTools

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
7539 dpurdie 1
#! /usr/bin/perl
2
########################################################################
3
# COPYRIGHT - VIX IP PTY LTD ("VIX"). ALL RIGHTS RESERVED.
4
#
5
# Module name   : blatQuarantine.pl
6
# Module type   :
7
# Compiler(s)   : Perl
8
# Environment(s):
9
#
10
# Description   : Age outpackages from dpkg_archive
11
#                 A replacement for the original quarantine process
12
#
13
# Usage         :   ARGV[0] - Path to config file for this instance
14
#
15
#! /usr/bin/perl
16
#......................................................................#
17
 
18
require 5.008_002;
19
use strict;
20
use warnings;
21
use Getopt::Long;
22
use File::Basename;
23
use Data::Dumper;
24
use File::Spec::Functions;
25
use POSIX ":sys_wait_h";
26
use File::Temp qw/tempfile/;
27
#use Digest::MD5 qw(md5_base64 md5_hex);
28
#use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
29
 
30
use FindBin;                                    # Determine the current directory
31
use lib "$FindBin::Bin/lib";                    # Allow local libraries
32
 
33
use Utils;
34
use StdLogger;                                  # Log to sdtout
35
use Logger;                                     # Log to file
36
 
37
#
38
#   Database interface
39
#   Pinched from jats and modified so that this software is not dependent on JATS
40
#
41
use IO::Handle;
42
use JatsRmApi;
43
use DBI;
44
 
45
#
46
#   Globals
47
#
48
my $logger = StdLogger->new();                  # Stdout logger. Only during config
49
$logger->err("No config file specified") unless (defined $ARGV[0]);
50
$logger->err("Config File does not exist: $ARGV[0]") unless (-f $ARGV[0]);
51
my $name = basename( $ARGV[0]);
52
   $name =~ s~.conf$~~;
53
my $now = 0;
54
my $startTime = 0;
55
my $nextQScan = 0;
56
my $lastS3Refresh =  0;
57
my $lastTagListUpdate = 0;
58
my $mtimeConfig = 0;
59
my $conf;
60
my $yday = -1;
61
my $linkUp = 1;
62
my $dbUp = 1;
63
my $RM_DB;
64
my $DM_DB;
65
my $activeReleases;
66
my $RMerror = 0;                        # Error on last RM DB access
67
my $ignorePkg;                          # Ref to hash of packages to ignore
68
my $explainFh;                          # Per quarantine info
69
 
70
#
71
#   Contain statisics maintained while operating
72
#       Can be dumped with a kill -USR2
73
#       List here for documentation
74
#  
75
 
76
my %statistics = (
77
    SeqNum => 0,                        # Bumped when $statistics are dumped
78
    timeStamp => 0,                     # DateTime when statistics are dumped
79
    upTime => 0,                        # Seconds since program start
80
    Cycle => 0,                         # Major process loop counter
81
    phase => 'Init',                    # Current phase of operation
82
    state => 'OK',                      # Nagios state
83
    wedged => 0,                        # Wedge indication - main loop not cycling
84
                                        # 
85
                                        # The following are reset each day
86
    dayStart => 0,                      # DateTime when daily data was reset
87
    linkErrors => 0,                    # Transfer (S3) errors
88
    dbErrors => 0,                      # Database errors
89
    processLoops => 0,                  # Number of time the quarantine process was run
90
);
91
 
92
#
93
#   Stats gatthered during the quarantine process
94
#   Held in a seperate structure to simplify handling
95
#   
96
my %qStats = (
97
                                        # 
98
                                        # Per Cycle Data - Calculated each processing Cycle
99
    # Error counters
100
    QuarantineError         => 0,
101
 
102
    # Major Statistics
103
    Quarantine              => 0,
104
    QuarantineTxRequested   => 0,
105
 
106
    # Minor Statistics
107
    'Reason.fileNotInReleaseManager' => 0,
108
    'Reason.inDeploymentManager'     => 0,
109
    'Reason.inSdk'                   => 0,
110
    'Reason.isPatch'                 => 0,
111
    'Reason.ManualBuild'             => 0,
112
    'Reason.RetainTime'              => 0,
113
    'Reason.NoBuildStandard'         => 0,
114
    'Reason.NoPackageEntry'          => 0,
115
    'Reason.NoPVid'                  => 0,
116
    'Reason.NotInArchive'            => 0,
117
    'Reason.NotInReleaseManager'     => 0,
118
    'Reason.NotLocked'               => 0,
119
    'Reason.SecondLevelPackage'      => 0,
120
    'Reason.TopLevelPackage'         => 0,
121
 
122
    TotalPackages           => 0,
123
 
124
    dpkgPackageCount        => 0,                       # Number of packages in dpkg_archive
125
    dpkgArchiveCount        => 0,                       # Number of package-versions in dpkg_archive
126
    ReleaseCount            => 0,                       # Number of releases to process
127
    RmPackageCount          => 0,                       # Number of packages extracted from RM
128
    topLevelCount           => 0,                       # Number of top level packages extracted from RM
129
    dmPackageCount          => 0,                       # Number of packages from Recent DM SBoms
130
    sdkCount                => 0,                       # Number of packages in SDKs 
131
    StrayCount              => 0,                       # Number of stray packages discovered
132
);
133
 
134
#
135
#   Describe configuration parameters
136
#
137
my %cdata = (
138
    '.ignore'         => {'pkg\.(.+)' => 'pkgs' },
139
 
140
    'piddir'          => {'mandatory' => 1      , 'fmt' => 'dir'},
141
    'sleep'           => {'default'   => 5      , 'fmt' => 'period'},
142
    'sleepLinkDown'   => {'default'   => '1m'   , 'fmt' => 'period'},
143
    'dpkg_archive'    => {'mandatory' => 1      , 'fmt' => 'dir'},
144
    'logfile'         => {'mandatory' => 1      , 'fmt' => 'vfile'},
145
    'logfile.size'    => {'default'   => '1M'   , 'fmt' => 'size'},
146
    'logfile.count'   => {'default'   => 9      , 'fmt' => 'int'},
147
    'wedgeTime'       => {'default'   => '120m'  , 'fmt' => 'period'},              # Can take a long time to process
148
 
149
    'verbose'         => {'default'   => 0      , 'fmt' => 'int'},                  # Debug ...
150
    'active'          => {'default'   => 1      , 'fmt' => 'bool'},                 # Disable alltogether
151
    'debug'           => {'default'   => 0      , 'fmt' => 'bool'},                 # Log to screen
152
    'txdetail'        => {'default'   => 0      , 'fmt' => 'bool'},                 # Show transfer times
153
    'noTransfers'     => {'default'   => 0      , 'fmt' => 'bool'},                 # Debugging option to prevent transfers
154
 
155
    'test'            => {'default'   => 0      , 'fmt' => 'bool'},                 # Used to test parts of the code
156
 
157
    'tagdir'          => {'mandatory' => 1      , 'fmt' => 'mkdir'},
158
 
159
    'runTime'         => {'default' => undef    , 'fmt' => 'period'},               # Time after midnight to run the quarantine process
160
    'forcedirscan'    => {'default'   => '24h'  , 'fmt' => 'period'},               # Period to run quantine scan
161
 
162
    'forces3update'   => {'default'   => '30m'  , 'fmt' => 'period'},
163
    'tagListUpdate'   => {'default'   => '1h'   , 'fmt' => 'period'},
164
    'S3Bucket'        => {'mandatory' => 1      , 'fmt' => 'text'},
165
    'S3Profile'       => {'mandatory' => 1      , 'fmt' => 'text'},
166
    'S3Region'        => {'default' => undef    , 'fmt' => 'text'},
167
 
168
    'snapAge'         => {'default'   => '1'    , 'fmt' => 'int'},                  # Days not a time
169
    'retainNoRm'      => {'default'   => '31d'  , 'fmt' => 'period'},
170
    'retain'          => {'default'   => '10d'  , 'fmt' => 'period'},
171
 
172
    'explain'         => {'default'   => 1      , 'fmt' => 'bool'},
173
);
174
 
175
 
176
#
177
#   Read in the configuration
178
#       Set up a logger
179
#       Write a pidfile - thats not used
180
$now = $startTime = time();
181
readConfig();
182
Utils::writepid($conf);
183
$logger->logmsg("Starting...");
184
readStatistics();
185
sighandlers();
186
$nextQScan = setQuarantineRunTime(0);
187
 
188
#
189
#   Main processing loop
190
#   Will exit when terminated by parent
191
#
192
while (1)
193
{
194
    $logger->verbose3("Processing");
195
    $statistics{Cycle}++;
196
    $now = time();
197
    Utils::resetWedge();
198
 
199
    $statistics{phase} = 'ReadConfig';
200
    readConfig();
201
    if ( $conf->{'active'} )
202
    {
203
        $statistics{phase} = 'Refresh S3 Info';
204
        refreshS3Info();
205
        if( $linkUp )
206
        {
207
            $statistics{phase} = 'Process Packages';
208
            processPackages();
209
 
210
            $statistics{phase} = 'maintainTagList';
211
            maintainTagList();
212
        }
213
    }
214
 
215
    $statistics{phase} = 'Sleep';
216
    sleep( ($linkUp && $dbUp) ? $conf->{'sleep'} : $conf->{'sleepLinkDown'} );
217
    reapChildren();
218
 
219
    #   If my PID file ceases to be, then exit the daemon
220
    #   Used to force daemon to restart
221
    #
222
    unless ( -f $conf->{'pidfile'} )
223
    {
224
        $logger->logmsg("Terminate. Pid file removed");
225
        last;
226
    }
227
}
228
$statistics{phase} = 'Terminated';
229
$logger->logmsg("Child End");
230
exit 0;
231
 
232
#-------------------------------------------------------------------------------
233
# Function        : reapChildren 
234
#
235
# Description     : Reap any and all dead children
236
#                   Call in major loops to prevent zombies accumulating 
237
#
238
# Inputs          : None
239
#
240
# Returns         : 
241
#
242
sub reapChildren
243
{
244
    my $currentPhase = $statistics{phase};
245
    $statistics{phase} = 'Reaping';
246
 
247
    my $kid;
248
    do {
249
        $kid = waitpid(-1, WNOHANG);
250
    } while ( $kid > 0 );
251
 
252
    $statistics{phase} = $currentPhase;
253
}
254
 
255
 
256
#-------------------------------------------------------------------------------
257
# Function        : readConfig
258
#
259
# Description     : Re read the config file if it modification time has changed
260
#
261
# Inputs          : Nothing
262
#
263
# Returns         : 0       - Config not read
264
#                   1       - Config read
265
#                             Config file has changed
266
#
267
sub readConfig
268
{
269
    my ($mtime) = Utils::mtime($ARGV[0]);
270
    my $rv = 0;
271
 
272
    if ( $mtimeConfig != $mtime )
273
    {
274
        $logger->logmsg("Reading config file: $ARGV[0]");
275
        $mtimeConfig = $mtime;
276
        my $errors;
277
        ($conf, $errors) = Utils::readconf ( $ARGV[0], \%cdata );
278
        if ( scalar @{$errors} > 0 )
279
        {
280
            warn "$_\n" foreach (@{$errors});
281
            die ("Config contained errors\n");
282
        }
283
 
284
        #
285
        #   Reset some information
286
        #   Create a new logger
287
        #
288
        $logger = Logger->new($conf) unless $conf->{debug};
289
        $conf->{logger} = $logger;
290
        $conf->{'pidfile'} = $conf->{'piddir'} . '/' . $name . '.pid';
291
        $logger->setVerbose($conf->{verbose});
292
        $logger->verbose("Log Levl: $conf->{verbose}");
293
 
294
        #
295
        #   Setup statistics filename
296
        $conf->{'statsfile'} = $conf->{'piddir'} . '/' . $name . '.stats';
297
        $conf->{'statsfiletmp'} = $conf->{'piddir'} . '/' . $name . '.stats.tmp';
298
 
299
        #
300
        #   Process 'pkgs' entry and set up $ignorePkg
301
        #
302
        $ignorePkg = {};
303
        while (my($key, $data) = each ( %{$conf->{pkgs}} ))
304
        {
305
            if ( $data eq 'KEEP' ) {
306
                $ignorePkg->{$key} = 1;
307
                $logger->verbose("Keep Pkg: $key");
308
 
309
            } else {
310
                $logger->warn("Unknown pkg mode: $key, $data");
311
            }
312
        }
313
 
314
        #
315
        #   When config is read force some actions
316
        #       - Force tagList to be created
317
        #       - Force refresh from S3
318
        $lastTagListUpdate = 0;
319
        $lastS3Refresh = 0;
320
        $rv = 1;
321
 
322
        #
323
        #   When config is read force some actions
324
#Utils::DebugDumpData ("Config", $conf);
325
 
326
        $logger->warn("All Transfers disabled") if ( $conf->{'noTransfers'} );
327
        $logger->warn("Package quarantine is inactive") unless ( $conf->{'active'} );
328
        $logger->warn("TEST MODE") if ( $conf->{'test'} );
329
    }
330
 
331
    return $rv;
332
}
333
 
334
#-------------------------------------------------------------------------------
335
# Function        : refreshS3Info 
336
#
337
# Description     : At startup, and at time after startup examine the S3 bucket
338
#                   and recover information from it 
339
#
340
# Inputs          : 
341
#
342
# Returns         : 0 - Gross error ( Bucket access) 
343
#
344
sub refreshS3Info
345
{
346
    my $rv = 1;
347
    if ( !$linkUp || ($now > ($lastS3Refresh + $conf->{'forces3update'})) )
348
    {
349
        $logger->verbose("refreshS3Info");
350
        $lastS3Refresh = $now;
351
 
352
        #
353
        #   Examine the s3 bucket and extract useful information
354
        #
355
        my $startTime = time;
356
        $rv =  examineS3Bucket();
357
         unless ($rv) {
358
            $statistics{linkErrors}++;
359
            $linkUp = 0;
360
         } else {
361
             $linkUp = 1;
362
         }
363
 
364
         #
365
         #   Display the duration of the refresh
366
         #       Diagnostic use
367
         #
368
         if ($conf->{txdetail}) {
369
             my $duration = time - $startTime;
370
             $logger->logmsg("refreshS3Info: Stats: $duration Secs");
371
         }
372
 
373
    }
374
    return $rv;
375
}
376
 
377
#-------------------------------------------------------------------------------
378
# Function        : examineS3Bucket 
379
#
380
# Description     : Scan the S3 bucket
381
#                   Currently only validates that the bucket exist
382
#                   and that the link is up.     
383
#                       
384
# Inputs          : Nothing 
385
#
386
# Returns         : 0 - Gross error ( Bucket access) 
387
#
388
sub examineS3Bucket
389
{
390
    my $bucket;
391
    my $prefix;
392
 
393
    if ($conf->{'S3Bucket'} =~ m~(.*?)/(.*)~) {
394
        $bucket = $1;
395
        $prefix = $2;
396
    } else {
397
        $bucket = $conf->{'S3Bucket'};
398
    }
399
 
400
    my $s3_cmd = "aws --profile $conf->{'S3Profile'} --output json";
401
    $s3_cmd .= " --region $conf->{'S3Region'}" if (defined $conf->{'S3Region'});
402
    $s3_cmd .= " s3api head-bucket --bucket $bucket";
403
 
404
    $logger->verbose2("examineS3Bucket:s3_cmd:$s3_cmd");
405
 
406
    my $ph;
407
    my $jsontxt = "";
408
    open ($ph, "$s3_cmd 2>&1 |");
409
    while ( <$ph> ) {
410
        chomp;
411
        $logger->verbose3("examineS3Bucket:Data: $_");
412
    }
413
    close ($ph);
414
    my $cmdRv = $?;
415
    if ($cmdRv != 0) {
416
        $logger->warn("Cannot read S3 Bucket Data");
417
        return 0;
418
    }
419
 
420
#Utils::DebugDumpData("activeReleases",$activeReleases);
421
    return 1;
422
}
423
 
424
#-------------------------------------------------------------------------------
425
# Function        : processPackages
426
#
427
# Description     : Process packages - the bulk of the quarantine effort
428
#                   This is simply time based
429
#
430
# Inputs          : None
431
#
432
# Returns         : Nothing
433
#
434
sub processPackages
435
{
436
    #
437
    #   Determine if new tags are present by examining the time
438
    #   that the directory was last modified.
439
    #
440
    #   Allow for a forced scan to catch packages that did not transfer
441
    #   on the first attempt
442
    #
443
    if ($now > $nextQScan)
444
    {
445
        $logger->verbose2("processPackages");
446
        $statistics{processLoops}++;
447
        resetData(1);
448
 
449
 
450
        my $fileExplain = $conf->{tagdir} . '/explain.txt';
451
        open ($explainFh, '>', $fileExplain);
452
 
453
        connectRM(\$RM_DB);
454
        connectDM(\$DM_DB);
455
        getReleaseDetails();
456
        GetAllPackageData();
457
        getTopLevelPackages();
458
        GetRecentDMPackages();
459
        LocateStrays();
460
        GetSdkPackageData();
461
        disconnectDM(\$DM_DB);
462
        disconnectRM(\$RM_DB);
463
 
464
        DumpInternalData();
465
        GeneratePvidLookup();
466
        processDpkgArchive();
467
 
468
        calcPkgsToQuarantine();
469
        doQuarantine();
470
 
471
#        reportMissingPkgs();
472
#        reportStats();
473
        close $explainFh;
474
        resetData(0);
475
 
476
        #
477
        #   Reset the scan time triggers
478
        #   
479
        $nextQScan = setQuarantineRunTime(1);
480
    }
481
}
482
 
483
#-------------------------------------------------------------------------------
484
# Function        : getDataFromRm 
485
#
486
# Description     : Get an array of data from RM
487
#                   Normally an array of arrays 
488
#
489
# Inputs          : $name           - Query Name
490
#                   $m_sqlstr       - Query
491
#                   $options        - Ref to a hash of options
492
#                                       sql     - show sql
493
#                                       data    - show data
494
#                                       dump    - show results
495
#                                       oneRow  - Only fetch one row
496
#                                       error   - Must find data
497
#                                       
498
# Returns         : ref to array of data
499
#
500
sub getDataFromRm
501
{
502
    my ($name,$m_sqlstr, $options ) = @_;
503
    my @row;
504
    my $data;
505
    $RMerror = 0;
506
 
507
    if (ref $options ne 'HASH') {
508
        $options = {}; 
509
    }
510
 
511
    if ($options->{sql}) {
512
        $logger->logmsg("$name: $m_sqlstr")
513
    }
514
    my $sth = $RM_DB->prepare($m_sqlstr);
515
    if ( defined($sth) )
516
    {
517
        if ( $sth->execute( ) ) {
518
            if ( $sth->rows ) {
519
                while ( @row = $sth->fetchrow_array ) {
520
                    if ($options->{data}) {
521
                        $logger->warn ("$name: @row");
522
                    }
523
                    #Debug0("$name: @row");
524
                    push @{$data}, [@row];
525
 
526
                    last if $options->{oneRow};
527
                }
528
            }
529
            $sth->finish();
530
        } else {
531
            $logger->warn("Execute failure:$name: $m_sqlstr", $sth->errstr() );
532
            $RMerror++;
533
            $statistics{dbErrors}++;
534
        }
535
    } else {
536
        $logger->warn("Prepare failure:$name" );
537
        $RMerror++;
538
        $statistics{dbErrors}++;
539
    }
540
 
541
    if (!$data && $options->{error}) {
542
        $logger->warn( $options->{error} );
543
    }
544
 
545
    if ($data && $options->{oneRow}) {
546
        $data = $data->[0];
547
    }
548
 
549
    if ($options->{dump}) {
550
        Utils::DebugDumpData("$name", $data);
551
    }
552
    return $data;
553
}
554
 
555
#-------------------------------------------------------------------------------
556
# Function        : executeRmQuery 
557
#
558
# Description     : Execute a simple RM query. One that does not expect any return data
559
#                   Assume DB connection has been established    
560
#
561
# Inputs          : $fname           - OprName, for error reporting
562
#                   $m_sqlstr        - SQL String
563
#
564
# Returns         : 1 - on Error
565
#                   0 - All good
566
#               
567
#
568
sub executeRmQuery
569
{
570
    my ($fname, $m_sqlstr) = @_;
571
 
572
    $logger->verbose3('ExecuteQuery:', $fname);
573
    $RMerror = 0;
574
    #
575
    #   Create the full SQL statement
576
    #
577
    my $sth = $RM_DB->prepare($m_sqlstr);
578
    if ( defined($sth) )
579
    {
580
        if ( $sth->execute() )
581
        {
582
            $sth->finish();
583
        }
584
        else
585
        {
586
            $logger->warn("$fname: Execute failure: $m_sqlstr", $sth->errstr() );
587
            $RMerror++;
588
            $statistics{dbErrors}++;
589
            return 1;
590
        }
591
    }
592
    else
593
    {
594
        $logger->warn("$fname: Prepare failure");
595
        $RMerror++;
596
        $statistics{dbErrors}++;
597
        return 1;
598
    }
599
 
600
    return 0;
601
}
602
 
603
#-------------------------------------------------------------------------------
604
# Function        : maintainTagList
605
#
606
# Description     : Maintain a data structure for the maintenance of the
607
#                   tags directory
608
#
609
# Inputs          : None
610
#
611
# Returns         : Nothing
612
#
613
sub maintainTagList
614
{
615
    #
616
    #   Time to perform the scan
617
    #   Will do at startup and every time period there after
618
    #
619
    return unless ( $now > ($lastTagListUpdate + $conf->{tagListUpdate} ));
620
    $logger->verbose("maintainTagList");
621
    $lastTagListUpdate = $now;
622
 
623
    #
624
    #   Generate new configuration
625
    #
626
    my %config;
627
    $config{s3Manifest} = 1;                # Indicate that it may be special
628
 
629
    %{$config{releases}} = map { $_ => 1 } keys %{$activeReleases};
630
 
631
    #
632
    #   Save data
633
    #
634
    my $dump =  Data::Dumper->new([\%config], [qw(*config)]);
635
#print $dump->Dump;
636
#$dump->Reset;
637
 
638
    #
639
    #   Save config data
640
    #
641
    my $conf_file = catfile( $conf->{'tagdir'},'.config' );
642
    $logger->verbose3("maintainTagList: Writting $conf_file");
643
 
644
    my $fh;
645
    open ( $fh, '>', $conf_file ) or $logger->err("Can't create $conf_file: $!");
646
    print $fh $dump->Dump;
647
    close $fh;
648
}
649
 
650
#-------------------------------------------------------------------------------
651
# Function        : resetDailyStatistics 
652
#
653
# Description     : Called periodically to reset the daily statistics
654
#
655
# Inputs          : $time       - Current time
656
#
657
# Returns         : 
658
#
659
sub resetDailyStatistics
660
{
661
    my ($time) = @_;
662
 
663
    #
664
    #   Detect a new day
665
    #
666
    my $today = (localtime($time))[7];
667
    if ($yday != $today)
668
    {
669
        $yday = $today;
670
        $logger->logmsg('Resetting daily statistics' );
671
 
672
        # Note: Must match @recoverTags in readStatistics
673
        $statistics{dayStart} = $time;
674
        $statistics{linkErrors} = 0;
675
        $statistics{dbErrors} = 0;
676
        $statistics{processLoops} = 0;
677
    }
678
}
679
 
680
#-------------------------------------------------------------------------------
681
# Function        : readStatistics 
682
#
683
# Description     : Read in the last set of stats
684
#                   Used after a restart to recover daily statistics
685
#
686
# Inputs          : 
687
#
688
# Returns         : 
689
#
690
sub readStatistics
691
{
692
    my @recoverTags = qw(dayStart linkErrors dbErrors processLoops);
693
 
694
    if ($conf->{'statsfile'} and -f $conf->{'statsfile'})
695
    {
696
        if (open my $fh, $conf->{'statsfile'})
697
        {
698
            while (<$fh>)
699
            {
700
                m~(.*):(.*)~;
701
                if ( grep( /^$1$/, @recoverTags ) ) 
702
                {
703
                    $statistics{$1} = $2;
704
                    $logger->verbose("readStatistics $1, $2");
705
                }
706
            }
707
            close $fh;
708
            $yday = (localtime($statistics{dayStart}))[7];
709
        }
710
    }
711
}
712
 
713
 
714
#-------------------------------------------------------------------------------
715
# Function        : periodicStatistics 
716
#
717
# Description     : Called on a regular basis to write out statistics
718
#                   Used to feed information into Nagios
719
#                   
720
#                   This function is called via an alarm and may be outside the normal
721
#                   processing loop. Don't make assumptions on the value of $now
722
#
723
# Inputs          : 
724
#
725
# Returns         : 
726
#
727
sub periodicStatistics
728
{
729
    #
730
    #   A few local stats
731
    #
732
    $statistics{SeqNum}++;
733
    $statistics{timeStamp} = time();
734
    $statistics{upTime} = $statistics{timeStamp} - $startTime;
735
    $statistics{wedged} = Utils::isWedged($conf);
736
 
737
    if ( $statistics{wedged}) {
738
         $statistics{state} = 'Wedged';
739
    } elsif(!$dbUp){
740
        $statistics{state} = 'RM Access error';
741
    } elsif(!$linkUp){
742
        $statistics{state} = 'S3 Bucket Read Error';
743
    } else {
744
        $statistics{state} = 'OK';
745
    }
746
 
747
 
748
    #   Reset daily accumulations - on first use each day
749
    resetDailyStatistics($statistics{timeStamp});
750
 
751
    #
752
    #   Write statistics to a file
753
    #       Write to a tmp file, then rename.
754
    #       Attempt to make the operation atomic - so that the file consumer
755
    #       doesn't get a badly formed file.
756
    #   
757
    if ($conf->{'statsfiletmp'})
758
    {
759
        my $fh;
760
        unless (open ($fh, '>', $conf->{'statsfiletmp'}))
761
        {
762
            $fh = undef;
763
            $logger->warn("Cannot create temp stats file: $!");
764
        }
765
        else
766
        {
767
            foreach my $key ( sort { lc($a) cmp lc($b) } keys %statistics)
768
            {
769
                print $fh $key . ':' . $statistics{$key} . "\n";
770
                $logger->verbose2('Statistics:'. $key . ':' . $statistics{$key});
771
            }
772
 
773
            #
774
            #   Also dump the stats related to the current (last)
775
            #   
776
            foreach my $key ( sort { lc($a) cmp lc($b) } keys %qStats)
777
            {
778
                my $txt = 'Qstats.' . $key . ':' . $qStats{$key};
779
                print $fh  $txt . "\n";
780
                $logger->verbose2('Statistics:'. $txt);
781
            }
782
            #
783
 
784
            close $fh;
785
 
786
            # Rename temp to real file
787
            rename  $conf->{'statsfiletmp'},  $conf->{'statsfile'} ;
788
        }
789
    }
790
}
791
 
792
#-------------------------------------------------------------------------------
793
# Function        : sighandlers
794
#
795
# Description     : Install signal handlers
796
#
797
# Inputs          : Uses gobals
798
#
799
# Returns         : Nothing
800
#
801
sub sighandlers
802
{
803
    $SIG{TERM} = sub {
804
        # On shutdown
805
        $logger->logmsg('Received SIGTERM. Shutting down....' );
806
        unlink $conf->{'pidfile'} if (-f $conf->{'pidfile'});
807
        exit 0;
808
    };
809
 
810
    $SIG{HUP} = sub {
811
        # On logrotate
812
        $logger->logmsg('Received SIGHUP.');
813
        $logger->rotatelog();
814
    };
815
 
816
    $SIG{USR1} = sub {
817
        # On Force Rescans
818
        $logger->logmsg('Received SIGUSR1.');
819
        $lastTagListUpdate = 0;
820
        $lastS3Refresh = 0;
821
        $nextQScan = 0;
822
    };
823
 
824
    alarm 60;
825
    $SIG{ALRM} = sub {
826
        # On Dump Statistics
827
        $logger->verbose2('Received SIGUSR2.');
828
        periodicStatistics();
829
        alarm 60;
830
    };
831
 
832
    $SIG{__WARN__} = sub { $logger->warn("@_") };
833
    $SIG{__DIE__} = sub { $logger->err("@_") };
834
}
835
 
836
 
837
#-------------------------------------------------------------------------------
838
# Function        : Error, Verbose, Warning
839
#
840
# Description     : Support for JatsRmApi
841
#
842
# Inputs          : Message
843
#
844
# Returns         : Nothing
845
#
846
sub Error
847
{
848
    $logger->err("@_");
849
}
850
 
851
sub Verbose
852
{
853
    $logger->verbose2("@_");
854
}
855
 
856
sub Warning
857
{
858
    $logger->warn("@_");
859
}
860
 
861
###############################################################################
862
#
863
#   Quarintine specific bits
864
#   
865
my @quarantineItems;
866
my @StrayPackages;
867
 
868
our %Releases;
869
our %Packages;
870
my %pkgPvid;
871
 
872
#-------------------------------------------------------------------------------
873
# Function        : resetData 
874
#
875
# Description     : Delete all the collected data so that we can run the process
876
#                   again 
877
#
878
# Inputs          : mode    - true. Reset quarantine stats too 
879
#
880
# Returns         : 
881
#
882
sub resetData
883
{
884
    my ($mode) = @_;
885
 
886
    @quarantineItems = ();
887
    @StrayPackages = ();
888
    %Releases = ();
889
    %Packages = ();
890
    %pkgPvid = ();
891
 
892
    if ($mode) {
893
        # Reset Stats for this run
894
        foreach my $key ( keys %qStats ) {
895
            $qStats{$key} = 0;
896
        }
897
    }
898
}
899
 
900
#-------------------------------------------------------------------------------
901
# Function        : setQuarantineRunTime 
902
#
903
# Description     : Set the runtime for the next run of the quarantine process
904
#                   Can configure the time at which the process will run
905
#                   In this mode it will run once a day at the specified time
906
#
907
# Inputs          : $mode : True: Calc next time
908
# 
909
#                   From conf.
910
#                   runTime - Time past midnight to run the process
911
#                   forcedirscan - Delay to next run
912
#                    
913
#
914
# Returns         : Next time to run the quarantine 
915
#
916
sub setQuarantineRunTime
917
{
918
    my ($mode) = @_;
919
    my $nextRunTime;
920
    if (defined $conf->{runTime}) {
921
        #
922
        #   Calc midnight
923
        #
924
        my @time = localtime();
925
        my $secsSinceMidnight = ($time[2] * 3600) + ($time[1] * 60) + $time[0];
926
        my $midnight = time() - $secsSinceMidnight;
927
        if ($mode) {
928
            $midnight += 24*60*60;
929
        }
930
 
931
        #
932
        #   Calc next run time
933
        #   
934
        $nextRunTime =  $midnight + $conf->{runTime};
935
    } else {
936
        $nextRunTime = time() + $conf->{forcedirscan};
937
    }
938
    $logger->verbose("setQuarantineRunTime: $nextRunTime, (" . localtime($nextRunTime) . ")");
939
    return $nextRunTime;
940
}
941
 
942
 
943
#-------------------------------------------------------------------------------
944
# Function        : getReleaseDetails
945
#
946
# Description     : Determine all candiate releases
947
#                   Assume connected to database
948
#
949
# Inputs          : 
950
#
951
# Returns         : 
952
#
953
sub getReleaseDetails
954
{
955
    my (@row);
956
 
957
    $logger->verbose("Determine all Release Names");
958
 
959
    # Get all Releases
960
    # From non-archived releases
961
    my $m_sqlstr = "SELECT prj.PROJ_NAME, rt.RTAG_NAME, rt.PROJ_ID, rt.RTAG_ID, rt.official, TRUNC (SYSDATE - rt.official_stamp) as OFFICIAL_STAMP_DAYS, TRUNC (SYSDATE - rt.created_stamp) as CREATED_STAMP_DAYS" .
962
                   " FROM release_manager.release_tags rt, release_manager.projects prj" .
963
                   " WHERE prj.PROJ_ID = rt.PROJ_ID " .
964
                   "   AND rt.official != 'A' ORDER BY UPPER(prj.PROJ_NAME), UPPER(rt.RTAG_NAME)";
965
#                   "   AND rt.official != 'Y'" .
966
 
967
    $logger->verbose2("getReleaseDetails: $m_sqlstr");
968
    my $sth = $RM_DB->prepare($m_sqlstr);
969
    if ( defined($sth) )
970
    {
971
        if ( $sth->execute( ) )
972
        {
973
            if ( $sth->rows )
974
            {
975
                while ( @row = $sth->fetchrow_array )
976
                {
977
                    my $rtag_id =$row[3];
978
                    my $proj_id = $row[2];
979
                    my $official = $row[4];
980
                    my $age = defined($row[5]) ? $row[5] : $row[6];
981
 
982
                    # Only retain recent snapshot
983
                    if ($official eq 'S' && $age > $conf->{snapAge}) {
984
                        next;
985
                    }
986
 
987
#if ( $official eq 'Y' ) {
988
#    Information("Closed Age ($proj_id) : $age : $row[0], $row[1]");
989
#}
990
#                    if ( $official eq 'Y' && $age && $age > 300 )
991
#                    {
992
#                        next;
993
#                    }
994
 
995
                    $Releases{$rtag_id}{pName} = $row[0];
996
                    $Releases{$rtag_id}{name} = $row[1];
997
                    $Releases{$rtag_id}{proj_id} = $proj_id;
998
                    $Releases{$rtag_id}{rtag_id} = $rtag_id;
999
                    $Releases{$rtag_id}{official} = $row[4];
1000
                    $Releases{$rtag_id}{officialDays} = defined($row[5]) ? $row[5] : $row[6] ;
1001
                    $Releases{$rtag_id}{createdDays} = $row[6];
1002
 
1003
                    #print join (',',@row), "\n" if ($opt_verbose > 2);
1004
                }
1005
            }
1006
            $sth->finish();
1007
        }
1008
        else
1009
        {
1010
            $logger->warn("getReleaseDetails:Execute failure: $m_sqlstr", $sth->errstr() );
1011
        }
1012
    }
1013
    else
1014
    {
1015
        $logger->warn("getReleaseDetails:Prepare failure" );
1016
    }
1017
 
1018
    $qStats{ReleaseCount} = scalar keys %Releases;
1019
}
1020
 
1021
#-------------------------------------------------------------------------------
1022
# Function        : GetAllPackageData
1023
#
1024
# Description     : Extract all package data
1025
#
1026
# Inputs          : 
1027
#
1028
# Returns         : 
1029
#
1030
 
1031
sub GetAllPackageData
1032
{
1033
    my (@row);
1034
    my $count = 0;
1035
 
1036
    $logger->verbose ("Extract all package data");
1037
 
1038
    # First get all packages
1039
    # From non-archived releases
1040
 
1041
    my $m_sqlstr = "SELECT DISTINCT " .
1042
                        "pv.PV_ID, " .                                          #[0]
1043
                        "pkg.PKG_NAME, " .                                      #[1]
1044
                        "pv.PKG_VERSION, " .                                    #[2]
1045
                        "pv.DLOCKED, " .                                        #[3]
1046
                        "pv.PKG_ID," .                                          #[4]
1047
                        "pv.is_patch," .                                        #[5]
1048
                        "pv.build_type,".                                       #[6]
1049
                        "pbi.bsa_id," .                                         #[7]
1050
#                        "pv.CREATOR_ID, " .                                     #[8]
1051
#                        "pv.MODIFIED_STAMP, " .                                 #[9]
1052
#                        "release_manager.PK_RMAPI.return_vcs_tag(pv.PV_ID), " . #[10]
1053
                        "999" .
1054
                   " FROM RELEASE_MANAGER.PACKAGE_VERSIONS pv,".
1055
                         "RELEASE_MANAGER.PACKAGES pkg,".
1056
                         "release_manager.package_build_info pbi" .
1057
                   " WHERE pv.PKG_ID = pkg.PKG_ID" .
1058
                   "   AND pv.pv_id = pbi.pv_id(+)";
1059
 
1060
    $logger->verbose2("GetAllPackageData: $m_sqlstr");
1061
    my $sth = $RM_DB->prepare($m_sqlstr);
1062
    if ( defined($sth) )
1063
    {
1064
        if ( $sth->execute( ) )
1065
        {
1066
            if ( $sth->rows )
1067
            {
1068
                while ( @row = $sth->fetchrow_array )
1069
                {
1070
                    $count++;
1071
                    #print join (',',@row), "\n" if ($opt_verbose > 2);
1072
                    my $pvid = $row[0];
1073
                    unless ( exists $Packages{$pvid}{name} )
1074
                    {
1075
                        $Packages{$pvid}{name} = $row[1];
1076
                        $Packages{$pvid}{version} = $row[2];
1077
                        $Packages{$pvid}{locked} = $row[3];
1078
                        $Packages{$pvid}{pkgid} = $row[4];
1079
                        $Packages{$pvid}{isPatch} = $row[5] || 0;
1080
                        $Packages{$pvid}{buildType} = $row[6] || 0;
1081
                        $Packages{$pvid}{buildStandard} = $row[7] || 0;
1082
 
1083
                        #$Packages{$pvid}{Creator} = $row[8];
1084
                        #$Packages{$pvid}{Age} = $row[9];
1085
                        #$Packages{$pvid}{vcstag} = $row[10];
1086
 
1087
                    }
1088
                }
1089
            }
1090
            $sth->finish();
1091
        }
1092
        else
1093
        {
1094
            $logger->warn("GetAllPackageData:Execute failure: $m_sqlstr", $sth->errstr() );
1095
        }
1096
    }
1097
    else
1098
    {
1099
        $logger->warn("GetAllPackageData:Prepare failure" );
1100
    }
1101
 
1102
    $logger->verbose ("All Packages: $count rows");
1103
    $qStats{RmPackageCount} = $count;
1104
}
1105
 
1106
#-------------------------------------------------------------------------------
1107
# Function        : getTopLevelPackages
1108
#
1109
# Description     : Extract top level packages from active releases
1110
#
1111
# Inputs          : 
1112
#
1113
# Returns         : 
1114
#
1115
 
1116
sub getTopLevelPackages
1117
{
1118
    my (@row);
1119
    my $count = 0;
1120
 
1121
    $logger->verbose ("Extract toplevel dependencies");
1122
 
1123
    # First get all packages that are referenced in a Release
1124
    # This will only get the top level packages
1125
    # From non-archived releases
1126
 
1127
    my $m_sqlstr = "SELECT DISTINCT " .
1128
                        "rc.PV_ID, " .                                          #[0]
1129
                        "rt.RTAG_ID, " .                                        #[1]
1130
                        "prj.PROJ_ID, " .                                       #[2]
1131
                        "rt.official, " .                                       #[3]    
1132
                        "TRUNC (SYSDATE - rt.official_stamp),".                 #[4]
1133
                        "TRUNC (SYSDATE - rt.created_stamp)" .                  #[5]
1134
                   " FROM RELEASE_MANAGER.RELEASE_CONTENT rc, ".
1135
                         "release_manager.release_tags rt,".
1136
                         "release_manager.projects prj" .
1137
                   " WHERE prj.PROJ_ID = rt.PROJ_ID" .
1138
                   "   and rt.RTAG_ID = rc.RTAG_ID" .
1139
                   "   AND rt.official != 'A'";
1140
 
1141
    $logger->verbose2("getTopLevelPackages: $m_sqlstr");
1142
    my $sth = $RM_DB->prepare($m_sqlstr);
1143
    if ( defined($sth) )
1144
    {
1145
        if ( $sth->execute( ) )
1146
        {
1147
            if ( $sth->rows )
1148
            {
1149
                while ( @row = $sth->fetchrow_array )
1150
                {
1151
                    my $pvid = $row[0];
1152
                    my $rtag_id = $row[1];
1153
                    my $proj_id = $row[2];
1154
                    my $official = $row[3];
1155
                    my $age = defined($row[4]) ? $row[4] : $row[6];
1156
 
1157
                    # Only retain recent snapshot
1158
                    if ($official eq 'S' && $age > $conf->{snapAge}) {
1159
                        next;
1160
                    }
1161
 
1162
                    $count++;
1163
                    $Packages{$pvid}{tlp} = 1;
1164
                    push @StrayPackages, $pvid;
1165
 
1166
                    push @{$Packages{$pvid}{release}}, $rtag_id;
1167
 
1168
                    push @{$Packages{$pvid}{projects}}, $proj_id
1169
                        unless (grep {$_ eq $proj_id} @{$Packages{$pvid}{projects}});
1170
 
1171
                }
1172
            }
1173
            $sth->finish();
1174
        }
1175
        else
1176
        {
1177
            $logger->warn("getTopLevelPackages:Execute failure: $m_sqlstr", $sth->errstr() );
1178
        }
1179
    }
1180
    else
1181
    {
1182
        $logger->warn("getTopLevelPackages:Prepare failure" );
1183
    }
1184
 
1185
    $logger->verbose ("Extract toplevel dependencies: $count rows");
1186
    $qStats{topLevelCount} = $count;
1187
}
1188
 
1189
#-------------------------------------------------------------------------------
1190
# Function        : GetRecentDMPackages
1191
#
1192
# Description     : Extract Packages that referenced in Deployment Manager
1193
#                   Want all package-versions from the last two BOMS in each state
1194
#                   of all projects. 
1195
#
1196
# Inputs          : 
1197
#
1198
# Returns         : 
1199
#
1200
 
1201
sub GetRecentDMPackages
1202
{
1203
    my (@row);
1204
    my $count = 0;
1205
 
1206
    $logger->verbose ("Extract DM Packages");
1207
 
1208
    # Get all packages that are a part of a non-deprecated SDK
1209
    # Only get the 'exposed' packages
1210
 
1211
    my $m_sqlstr =
1212
        "SELECT DISTINCT pv.pv_id," .                         #[0]
1213
        "  pkg.pkg_name," .                                   #[1]
1214
        "  pv.pkg_version" .                                  #[2]
1215
        " FROM DEPLOYMENT_MANAGER.bom_contents bc," .
1216
        "     DEPLOYMENT_MANAGER.operating_systems os," .
1217
        "     DEPLOYMENT_MANAGER.os_contents osc," .
1218
        "     DEPLOYMENT_MANAGER.PACKAGES pkg," .
1219
        "     DEPLOYMENT_MANAGER.PACKAGE_VERSIONS pv," .
1220
        "     DEPLOYMENT_MANAGER.PRODUCT_DETAILS pd" .
1221
        " WHERE osc.os_id = os.os_id" .
1222
        " AND os.node_id  = bc.node_id" .
1223
        " AND bc.bom_id  IN" .
1224
        "  (SELECT bom_id" .
1225
        "  FROM" .
1226
        "    (SELECT bs.bom_id, b.branch_id, state_id, bn.bom_name ," .
1227
        "            RANK() OVER (PARTITION BY bs.state_id,b.branch_id, bn.bom_name ORDER BY bs.bom_id DESC) SRLNO" .
1228
        "     FROM DEPLOYMENT_MANAGER.bom_state bs ," .
1229
        "          DEPLOYMENT_MANAGER.boms b," .
1230
        "          DEPLOYMENT_MANAGER.bom_names bn" .
1231
        "     WHERE bs.bom_id   = b.bom_id" .
1232
        "       AND b.BOM_NAME_ID = bn.BOM_NAME_ID" .
1233
        "    )" .
1234
        "  WHERE SRLNO <= 3" .
1235
        "  )" .
1236
        " AND pd.PROD_ID (+) = osc.PROD_ID" .
1237
        " AND pv.pkg_id      = pkg.pkg_id" .
1238
        " AND osc.prod_id    = pv.pv_id" .
1239
        " ORDER BY UPPER(pkg.pkg_name), " .
1240
        "          UPPER(pv.PKG_VERSION)";
1241
 
1242
    $logger->verbose2("GetRecentDMPackages: $m_sqlstr");
1243
    my $sth = $DM_DB->prepare($m_sqlstr);
1244
    if ( defined($sth) )
1245
    {
1246
        if ( $sth->execute( ) )
1247
        {
1248
            if ( $sth->rows )
1249
            {
1250
                while ( @row = $sth->fetchrow_array )
1251
                {
1252
                    $count++;
1253
                    #print join (',',@row), "\n" if ($opt_verbose > 2);
1254
                    my $pvid = $row[0];
1255
                    $Packages{$pvid}{dm} = 1;
1256
                    unless ( exists $Packages{$pvid}{name} )
1257
                    {
1258
                        $Packages{$pvid}{name} = $row[1];
1259
                        $Packages{$pvid}{version} = $row[2];
1260
                    }
1261
                    push @StrayPackages, $pvid;
1262
                }
1263
            }
1264
            $sth->finish();
1265
        }
1266
        else
1267
        {
1268
            $logger->warn("GetRecentDMPackages:Execute failure: $m_sqlstr", $sth->errstr() );
1269
        }
1270
    }
1271
    else
1272
    {
1273
        $logger->warn("GetRecentDMPackages:Prepare failure" );
1274
    }
1275
 
1276
    $logger->verbose ("Extract Deployed Packages: $count rows");
1277
    $qStats{dmPackageCount} = $count;
1278
}
1279
 
1280
#-------------------------------------------------------------------------------
1281
# Function        : LocateStrays
1282
#
1283
# Description     : Locate stray packages
1284
#                   Try to do several (200) at a time to speed up processing
1285
#
1286
# Inputs          :
1287
#
1288
# Returns         :
1289
#
1290
sub LocateStrays
1291
{
1292
    $logger->verbose  ("Locate indirectly referenced packages");
1293
    my $count = 0;
1294
    while ( $#StrayPackages >= 0 )
1295
    {
1296
        $logger->verbose3 ("Strays Remaining: " . scalar @StrayPackages );
1297
 
1298
        my @plist;
1299
        while ( $#plist <= 200 && @StrayPackages )
1300
        {
1301
            my $pv_id = pop @StrayPackages;
1302
            next if ( exists $Packages{$pv_id}{done} );
1303
            push @plist, $pv_id;
1304
        }
1305
 
1306
        GetDepends(@plist) if @plist;
1307
 
1308
        foreach ( @plist)
1309
        {
1310
            $Packages{$_}{done} = 1;
1311
            $count++;
1312
        }
1313
    }
1314
 
1315
    $qStats{StrayCount} = $count;
1316
}
1317
 
1318
#-------------------------------------------------------------------------------
1319
# Function        : GetDepends
1320
#
1321
# Description     :
1322
#
1323
# Inputs          : @plist          - list of pvid's to process
1324
#
1325
# Returns         :
1326
#
1327
sub GetDepends
1328
{
1329
    my (@plist) = @_;
1330
 
1331
    #
1332
    #   Now extract the package dependacies
1333
    #   There may not be any
1334
    #
1335
    my $m_sqlstr = "SELECT ".
1336
                    " pd.PV_ID, ".
1337
                    " pd.DPV_ID " .
1338
                  " FROM    RELEASE_MANAGER.PACKAGE_DEPENDENCIES pd ".
1339
                  " WHERE pd.PV_ID in ( " . join(',', @plist) . " )";
1340
    my $sth = $RM_DB->prepare($m_sqlstr);
1341
    if ( defined($sth) )
1342
    {
1343
        if ( $sth->execute( ) )
1344
        {
1345
            if ( $sth->rows )
1346
            {
1347
                while ( my @row = $sth->fetchrow_array )
1348
                {
1349
                    my $pvid = $row[0];
1350
                    my $dpvid = $row[1];
1351
                    push @StrayPackages, $dpvid;
1352
                    push @{$Packages{$dpvid}{usedBy}}, $pvid;
1353
                    $Packages{$dpvid}{slp} = 1 unless exists $Packages{$dpvid}{tlp};
1354
 
1355
                    #print join (',','GetDepends',@row), "\n" if ($opt_verbose > 2);
1356
                }
1357
            }
1358
            $sth->finish();
1359
        }
1360
        else
1361
        {
1362
            $logger->warn("GetDepends:Execute failure: $m_sqlstr", $sth->errstr() );
1363
        }
1364
    }
1365
    else
1366
    {
1367
        $logger->warn("GetDepends:Prepare failure" );
1368
    }
1369
}
1370
 
1371
#-------------------------------------------------------------------------------
1372
# Function        : GetSdkPackageData
1373
#
1374
# Description     : Extract Packages that are a part of a non-deprecated SDK
1375
#                   Only want the exposed packages
1376
#
1377
#                   Don't care about the dependencies, so don't add them 
1378
#                   to strays
1379
#
1380
# Inputs          : 
1381
#
1382
# Returns         : 
1383
#
1384
 
1385
sub GetSdkPackageData
1386
{
1387
    my (@row);
1388
    my $count = 0;
1389
 
1390
    $logger->verbose ("Extract SDK Packages");
1391
 
1392
    # Get all packages that are a part of a non-deprecated SDK
1393
    # Only get the 'exposed' packages
1394
 
1395
    my $m_sqlstr = "SELECT sc.pv_id, " .                #[0]
1396
                   "       p.PKG_NAME, " .              #[1]
1397
                   "       pv.PKG_VERSION" .            #[2]
1398
                   " FROM RELEASE_MANAGER.SDK_CONTENT sc," .
1399
                   "   RELEASE_MANAGER.sdk_tags st," .
1400
                   "   RELEASE_MANAGER.package_versions pv," .
1401
                   "   RELEASE_MANAGER.PACKAGES p" .
1402
                   " WHERE sc.SDKTAG_ID    = st.SDKTAG_ID" .
1403
                   " AND p.PKG_ID = pv.PKG_ID" .
1404
                   " AND pv.PV_ID = sc.pv_id" .
1405
                   " AND sc.SDKPKG_STATE   = 'E'" .
1406
                   " AND st.SDK_STATE NOT IN ('D')" ;
1407
 
1408
    $logger->verbose2("GetSdkPackageData: $m_sqlstr");
1409
    my $sth = $RM_DB->prepare($m_sqlstr);
1410
    if ( defined($sth) )
1411
    {
1412
        if ( $sth->execute( ) )
1413
        {
1414
            if ( $sth->rows )
1415
            {
1416
                while ( @row = $sth->fetchrow_array )
1417
                {
1418
                    $count++;
1419
                    #print join (',',@row), "\n" if ($opt_verbose > 2);
1420
                    my $pvid = $row[0];
1421
                    $Packages{$pvid}{sdk} = 1;
1422
                    unless ( exists $Packages{$pvid}{name} )
1423
                    {
1424
                        $Packages{$pvid}{name} = $row[1];
1425
                        $Packages{$pvid}{version} = $row[2];
1426
                    }
1427
                }
1428
            }
1429
            $sth->finish();
1430
        }
1431
        else
1432
        {
1433
            $logger->warn("GetSdkPackageData:Execute failure: $m_sqlstr", $sth->errstr() );
1434
        }
1435
    }
1436
    else
1437
    {
1438
        $logger->warn("GetSdkPackageData:Prepare failure" );
1439
    }
1440
 
1441
    $logger->verbose ("Extract SDK Packages: $count rows");
1442
    $qStats{sdkCount} = $count;
1443
}
1444
 
1445
#-------------------------------------------------------------------------------
1446
# Function        : GeneratePvidLookup  
1447
#
1448
# Description     : Populate $pkgPvid (hash)
1449
#
1450
# Inputs          : 
1451
#
1452
# Returns         : 
1453
#
1454
sub GeneratePvidLookup
1455
{
1456
    #
1457
    # Create a lookup from package name/version to pvid
1458
    #
1459
    foreach my $pvid ( keys %Packages )
1460
    {
1461
        my $name = $Packages{$pvid}{name};
1462
        my $version = $Packages{$pvid}{version};
1463
        if ( $name && $version )
1464
        {
1465
            $pkgPvid{$name}{$version}{pvid} = $pvid;
1466
        }
1467
    }
1468
}
1469
 
1470
#-------------------------------------------------------------------------------
1471
# Function        : processDpkgArchive
1472
#
1473
# Description     : Scan dpkg_archive
1474
#
1475
# Inputs          : 
1476
#
1477
# Returns         : 
1478
#
1479
sub processDpkgArchive
1480
{
1481
    $logger->verbose ("Scanning dpkg_archive");
1482
    unless (opendir( PKGS, $conf->{dpkg_archive} ) ) {
1483
         $logger->warn("Cannot open dpkg_archive: $conf->{dpkg_archive}");
1484
         return;
1485
    }
1486
 
1487
    while ( my $pkgName = readdir(PKGS) )
1488
    {
1489
        next if ( $pkgName eq '.' );
1490
        next if ( $pkgName eq '..' );
1491
        next if ( $pkgName eq 'lost+found' );
1492
        next if ( exists $ignorePkg->{$pkgName} );
1493
 
1494
        my $pkgDir = join('/', $conf->{dpkg_archive}, $pkgName );
1495
        if ( -d $pkgDir )
1496
        {
1497
            if (opendir (PV, $pkgDir ) )
1498
            {
1499
                $qStats{dpkgPackageCount}++;
1500
                while ( my $pkgVersion = readdir(PV) )
1501
                {
1502
                    next if ( $pkgVersion eq '.' );
1503
                    next if ( $pkgVersion eq '..' );
1504
                    next if ( $pkgVersion eq 'latest' );            # Keep latest (often symlink for build system)
1505
                    $qStats{dpkgArchiveCount}++;
1506
 
1507
                    my $pkgPath = join('/', $conf->{dpkg_archive}, $pkgName,$pkgVersion );
1508
                    my $mtime = checkTime($pkgPath);
1509
 
1510
                    my $pvid;
1511
                    if ( exists ($pkgPvid{$pkgName}) && exists($pkgPvid{$pkgName}{$pkgVersion} ) )
1512
                    {
1513
                        $pvid = $pkgPvid{$pkgName}{$pkgVersion}{pvid};
1514
                        $Packages{$pvid}{dpkg_archive} = 1;
1515
                        $pkgPvid{$pkgName}{$pkgVersion}{mtime} = $mtime;
1516
                    }
1517
                    else
1518
                    {
1519
                        #
1520
                        #   Package is in dpkg-archive, but not in Release
1521
                        #   Manager. Allow for a short while
1522
                        #
1523
                        $qStats{TotalPackages}++;
1524
                        $qStats{'Reason.' . 'NotInReleaseManager'}++;
1525
                        if ( $mtime > $conf->{retainNoRm} )
1526
                        {
1527
                            #Log("Package not in RM: $pkgName, $pkgVersion, Age: $mtime");
1528
                            quarantineItem( 'X', $pkgName, $pkgVersion );
1529
                            $qStats{'Quarantine'}++;
1530
                        }
1531
 
1532
                        explain ("Reason:-, $pkgName, $pkgVersion, Reason:NotInReleaseManager");
1533
                    }
1534
 
1535
#Message("$pkgName, $pkgVersion, $pkgPvid{$pkgName}{$pkgVersion}{mtime}");
1536
                }
1537
                close(PV);
1538
            }
1539
        }
1540
        elsif ( -f $pkgDir )
1541
        {
1542
            $logger->warn("Unexpected file in dpkg_archive: $pkgName");
1543
 
1544
            # Ideally we should delete the file
1545
#            quarantineItem( 'F', -1, $pkgDir );
1546
            $qStats{'Reason.' .'fileNotInReleaseManager'}++;
1547
            $qStats{'Quarantine'}++;
1548
            $qStats{'Reason.' .'NotInReleaseManager'}++;
1549
            explain("Reason:-, $pkgDir, -, Reason:fileNotInReleaseManager");
1550
        }
1551
        else
1552
        {
1553
            $logger->warn("Unexpected entry in dpkg_archive: $pkgName");
1554
        }
1555
    }
1556
    close(PKGS);
1557
}
1558
 
1559
#-------------------------------------------------------------------------------
1560
# Function        : calcPkgsToQuarantine 
1561
#
1562
# Description     : Calculate the packages to be quarantined 
1563
#
1564
# Inputs          : 
1565
#
1566
# Returns         : 
1567
#
1568
sub calcPkgsToQuarantine
1569
{
1570
    #
1571
    #
1572
    #   Scan all packages found in dpkg_archive and see if we should keep it
1573
    #   Quarantine those we cannot find a reason to keep
1574
    #
1575
    foreach my $pkgName ( sort keys %pkgPvid )
1576
    {
1577
        foreach my $pkgVersion ( sort keys %{$pkgPvid{$pkgName}} )
1578
        {
1579
            my $mtime = $pkgPvid{$pkgName}{$pkgVersion}{mtime} || 0;
1580
            my $pvid = $pkgPvid{$pkgName}{$pkgVersion}{pvid};
1581
            my $keepReason = '';
1582
            my $entry = $Packages{$pvid};
1583
 
1584
            {
1585
                # Examine entry. Determine a reason to keep the package
1586
                #   Some reasons to keep a package are no longer needed now that versions are pumped into S3
1587
 
1588
                unless ($entry) { $keepReason ='NoPackageEntry'; last;}
1589
                unless ($entry->{dpkg_archive}) { $keepReason ='NotInArchive'; last;}
1590
                unless ($pvid) { $keepReason = 'NoPVid'; last;}
1591
                if (exists $entry->{tlp}) { $keepReason = 'TopLevelPackage'; last;}
1592
                if (exists $entry->{slp}) { $keepReason = 'SecondLevelPackage'; last;}
1593
                if (exists $entry->{sdk}) { $keepReason ='inSdk'; last;}
1594
                if (exists $entry->{dm}) { $keepReason = 'inDeploymentManager'; last;}
1595
                if ($entry->{isPatch}) { $keepReason = 'isPatch'; last;}
1596
                if ($mtime <= $conf->{retain}) { $keepReason ='RetainTime:' . ($conf->{retain} - $mtime); last;}
1597
                #unless ($entry->{buildStandard}) { $keepReason ='NoBuildStandard:' . $mtime; last;}
1598
                if ($entry->{locked} ne 'Y') { $keepReason ='NotLocked:' . $entry->{locked}; last;}
1599
                #if ($entry->{buildType} eq 'M') { $keepReason ='ManualBuild:' . $entry->{buildType}; last;}
1600
 
1601
                $pkgPvid{$pkgName}{$pkgVersion}{keepReason} = $keepReason;
1602
            }
1603
 
1604
            unless ( $keepReason )
1605
            {
1606
                $logger->verbose2("Quarantine:$pvid, $pkgName, $pkgVersion, Age:$mtime, Lock:$entry->{locked}, Patch:$entry->{isPatch}, BS:$entry->{buildStandard}, BT:$entry->{buildType}");
1607
                quarantineItem( 'Q', $mtime, $pkgName, $pkgVersion) ;
1608
                $keepReason = 'Quarantine';
1609
            }
1610
 
1611
            explain("Reason:$pvid, $pkgName, $pkgVersion, Reason:$keepReason");
1612
 
1613
            #
1614
            #   Maintain Stats
1615
            #       Only use the Base Reason - remove details after the ':' character
1616
            #
1617
            my $sReason = $keepReason;
1618
            $sReason =~ s~:.*$~~;
1619
            $qStats{'Reason.' . $sReason}++;
1620
            $qStats{TotalPackages}++;
1621
        }
1622
    }
1623
}
1624
 
1625
#-------------------------------------------------------------------------------
1626
# Function        : quarantineItem
1627
#
1628
# Description     : Add item to the list of stuff to be quarantined
1629
#
1630
# Inputs          : $reason         - Reason
1631
#                   $age            - Age
1632
#                   $pkgName        - Package Nname
1633
#                   $pkgVersion     - Package Version
1634
#
1635
# Returns         : 
1636
#
1637
sub quarantineItem
1638
{
1639
    my ($reason, $age, $pkgName, $pkgVersion ) = @_;
1640
    my %data;
1641
    $data{reason} = $reason;
1642
    $data{age} = $age ;
1643
    $data{name} = $pkgName;
1644
    $data{version} = $pkgVersion;
1645
 
1646
    push @quarantineItems, \%data;
1647
}
1648
 
1649
#-------------------------------------------------------------------------------
1650
# Function        : checkTime
1651
#
1652
# Description     : Seconds since modification of a path
1653
#
1654
# Inputs          : Path elements
1655
#
1656
# Returns         : Days since modification
1657
#
1658
 
1659
sub checkTime
1660
{
1661
    my ($path) = @_;
1662
    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
1663
        $atime,$mtime,$ctime,$blksize,$blocks) = stat($path);
1664
 
1665
    unless(defined $mtime)
1666
    {
1667
        $logger->warn("Bad stat for $path");
1668
        $mtime = 0;
1669
    }
1670
 
1671
    return $now - $mtime;
1672
}
1673
#-------------------------------------------------------------------------------
1674
# Function        : DumpInternalData
1675
#
1676
# Description     : Save data for examination
1677
#                   Has out of memory issues 
1678
#
1679
# Inputs          : 
1680
#
1681
# Returns         : 
1682
#
1683
sub DumpInternalData
1684
{
1685
#       my $fh;
1686
#       my $fileName = $conf->{tagdir} . '/releases.txt';
1687
#       $logger->logmsg("Dump Releases: $fileName");
1688
#       open ($fh, '>', $fileName);
1689
#       print $fh Data::Dumper->Dump ( [\%Releases] );
1690
#       close $fh;
1691
#
1692
#       $fileName = $conf->{tagdir} . '/packages.txt';
1693
#       $logger->logmsg("Dump Packages: $fileName");
1694
#       open ($fh, '>', $fileName);
1695
#       print $fh Data::Dumper->Dump ( [\%Packages] );
1696
#       close $fh;
1697
}
1698
 
1699
#-------------------------------------------------------------------------------
1700
# Function        : doQuarantine
1701
#
1702
# Description     : Quarantine files and folders that have been queued up
1703
#                   If the tar zip of the package exists in the s3 bucket - then delete it
1704
#                   Otherwise request that a tar zip be created. Should be picked up on the
1705
#                   next scan.
1706
#
1707
# Inputs          : None
1708
#
1709
# Returns         : 
1710
#
1711
sub doQuarantine
1712
{
1713
    my $testMsg = $conf->{test} ? 'Test,' : '';
1714
 
1715
    # Process entries - oldest first
1716
    #
1717
    $qStats{'QuarantineToDo'} = ( scalar @quarantineItems );
1718
    $logger->logmsg ("Packages to quarantine:  $qStats{'QuarantineToDo'}");
1719
    foreach my $entry (sort {$b->{age} <=> $a->{age} } @quarantineItems)
1720
    {
1721
        my $emsg = '';
1722
        if (pkgInS3($entry->{name}, $entry->{version}) ) {
1723
            #   Package is safely in S3 - can simply delete it
1724
            if ($conf->{test}) {
1725
                $emsg = ' - Not deleted in test mode';
1726
                $qStats{'QuarantineCount'}++;      
1727
 
1728
            } else {
1729
                delete_version($entry->{name}, $entry->{version});
1730
                my $path = join('/', $conf->{dpkg_archive}, $entry->{name}, $entry->{version} );
1731
                if (-d $path) {
1732
                    $logger->warn("Could not delete package: $path");
1733
                    $qStats{'QuarantineError'}++;      
1734
                    $emsg = ' - Delete error';
1735
 
1736
                } else {
1737
                    $qStats{'QuarantineCount'}++;      
1738
                    $emsg = '';
1739
                }
1740
            }
1741
 
1742
        } else {
1743
            # Package has not been transferred to S3
1744
            # Would have thought this to be very unlikely, but still
1745
            # Since the package is not safely stored away we can't delete it at this point in time
1746
            # Request that it be transferred
1747
            # With luck (or by design) the package will be in S3 by the time the process runs again.
1748
            # 
1749
            requestS3Transfer($entry->{name}, $entry->{version});
1750
            $qStats{'QuarantineTxRequested'}++;
1751
            $emsg = ' - Not in S3. Transfer requested';
1752
        }
1753
 
1754
        # Log operation with frills
1755
        $logger->logmsg (sprintf("Quarantined:%s%s,%10.10s,%s %s%s", $testMsg, $entry->{reason}, $entry->{age}, $entry->{name}, $entry->{version}, $emsg ));
1756
        $qStats{'QuarantineToDo'}--;
1757
    }
1758
    $logger->verbose("End doQuarantine");
1759
}
1760
 
1761
#-------------------------------------------------------------------------------
1762
# Function        : requestS3Transfer  
1763
#
1764
# Description     : Request that another blat daemon transfer a package to S3 
1765
#
1766
# Inputs          : $pname
1767
#                   $pver 
1768
#
1769
# Returns         : Nothing 
1770
#
1771
sub requestS3Transfer
1772
{
1773
    my ($pname, $pver) = @_;
1774
 
1775
    $conf->{'tagdir'} =~ m~^(.*)/~;
1776
    my $tagRoot = $1;
1777
    my $tag = "$pname::$pver";
1778
    my $s3TransferTagDir = catfile($tagRoot, 's3Transfer' );
1779
    my $s3TransferTag = catfile($s3TransferTagDir, $tag);
1780
    $logger->warn ("requestS3Transfer: Invalid directory: $s3TransferTagDir") unless -d $s3TransferTagDir;
1781
 
1782
    if ( $conf->{'noTransfers'} ) {
1783
        $logger->logmsg("Request S3 transfer DISABLED: $s3TransferTag")
1784
    } else {
1785
        $logger->logmsg("Request S3 transfer: $s3TransferTag");
1786
        Utils::TouchFile($conf, $s3TransferTag) unless -f $s3TransferTag;
1787
    }
1788
}
1789
 
1790
#-------------------------------------------------------------------------------
1791
# Function        : pkgInS3  
1792
#
1793
# Description     : Check that a specified package-versions exists in the dpkg_archive
1794
#                   S3 bucket
1795
#
1796
# Inputs          : $pname
1797
#                   $pversion
1798
#
1799
# Returns         : 1 - Package is in S3
1800
#                   0 - Package not found 
1801
#
1802
sub pkgInS3
1803
{
1804
    my ($pname, $pversion) = @_;
1805
    my $objKey = $pname . '__' . $pversion . '.tgz';
1806
 
1807
 
1808
    my $s3_cmd = "aws --profile $conf->{'S3Profile'} --output json";
1809
    $s3_cmd .= " s3api head-object --bucket vix-dpkg-archive --key $objKey";
1810
 
1811
    $logger->verbose2("pkgInS3:s3_cmd:$s3_cmd");
1812
 
1813
    my $ph;
1814
    my $jsontxt = "";
1815
    if (open ($ph, "$s3_cmd 2>/dev/null |") )
1816
    {
1817
        while ( <$ph> ) {
1818
            chomp;
1819
            $logger->verbose2("pkgInS3:Data: $_");
1820
            $jsontxt .= $_;
1821
        }
1822
        close ($ph);
1823
    }
1824
    if ($jsontxt) {
1825
        $logger->verbose2("pkgInS3: $pname, $pversion Found");
1826
        return 1;
1827
    }
1828
 
1829
    $logger->verbose2("pkgInS3: $pname, $pversion Not Found");
1830
    return 0;
1831
}
1832
 
1833
#-------------------------------------------------------------------------------
1834
# Function        : delete_version  
1835
#
1836
# Description     : Delete one version of one package 
1837
#
1838
# Inputs          : $pname
1839
#                   $pver
1840
#
1841
# Returns         : Not used 
1842
#
1843
sub delete_version {
1844
    my($pname, $pver) = @_;
1845
        #
1846
        #   Need to use a helper utilty to delete the package-version
1847
        #       The helper is run as root as it greatly simplified the deletion process
1848
        #       The helper is run via sudo
1849
    my $cmd = "sudo -n ./delete_package.sh $conf->{dpkg_archive} $pname $pver";
1850
    if (open (my $ph, "$cmd 2>&1 |") ) {
1851
      while ( <$ph> ) {
1852
          chomp;
1853
          $logger->verbose2("delete_version: $_");
1854
      }
1855
      close ($ph);
1856
    }
1857
}
1858
 
1859
#-------------------------------------------------------------------------------
1860
# Function        : explain 
1861
#
1862
# Description     : Display / log the reason a package is being processed
1863
#
1864
# Inputs          : $txt 
1865
#
1866
# Returns         : Nothinf
1867
#
1868
sub explain
1869
{
1870
    my($txt) = @_;
1871
    if ($conf->{explain})
1872
    {
1873
        $logger->verbose2($txt);
1874
        print $explainFh $txt . "\n";
1875
    }
1876
}
1877