Subversion Repositories DevTools

Rev

Rev 7469 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
7469 dpurdie 1
#! /usr/bin/perl
2
########################################################################
3
# COPYRIGHT - VIX IP PTY LTD ("VIX"). ALL RIGHTS RESERVED.
4
#
5
# Module name   : blats3Manifest.pl
6
# Module type   :
7
# Compiler(s)   : Perl
8
# Environment(s):
9
#
10
# Description   :   This is a blat related task that will perform S3 SYNC
11
#                   transfers for configured releases
12
#
13
# Usage         :   ARGV[0] - Path to config file for this instance
14
#
15
#......................................................................#
16
 
17
require 5.008_002;
18
use strict;
19
use warnings;
20
use Getopt::Long;
21
use File::Basename;
22
use Data::Dumper;
23
use File::Spec::Functions;
24
use POSIX ":sys_wait_h";
25
use File::Temp qw/tempfile/;
26
use Digest::MD5 qw(md5_base64 md5_hex);
27
use File::Path qw( rmtree );
28
use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
29
use JSON;
30
 
31
use FindBin;                                    # Determine the current directory
32
use lib "$FindBin::Bin/lib";                    # Allow local libraries
33
 
34
use Utils;
35
use StdLogger;                                  # Log to sdtout
36
use Logger;                                     # Log to file
37
 
38
#
39
#   Database interface
40
#   Pinched from jats and modified so that this software is not dependent on JATS
41
#
42
use IO::Handle;
43
use JatsRmApi;
44
use DBI;
45
 
46
#
47
#   Globals
48
#
49
my $logger = StdLogger->new();                  # Stdout logger. Only during config
50
$logger->err("No config file specified") unless (defined $ARGV[0]);
51
$logger->err("Config File does not exist: $ARGV[0]") unless (-f $ARGV[0]);
52
my $name = basename( $ARGV[0]);
53
   $name =~ s~.conf$~~;
54
my $now = 0;
55
my $startTime = 0;
56
my $tagDirTime = 0;
57
my $lastDirScan = 0;
58
my $lastS3Refresh =  0;
59
my $lastTagListUpdate = 0;
60
my $mtimeConfig = 0;
61
my $conf;
62
my $yday = -1;
63
my $linkUp = 1;
7485 dpurdie 64
my $dbUp = 1;
7469 dpurdie 65
my $RM_DB;
66
my $activeReleases;
7485 dpurdie 67
my $RMerror = 0;                        # Error on last RM DB access
7469 dpurdie 68
 
69
#
70
#   Contain statisics maintained while operating
71
#       Can be dumped with a kill -USR2
72
#       List here for documentation
73
#  
74
 
75
my %statistics = (
76
    SeqNum => 0,                        # Bumped when $statistics are dumped
77
    timeStamp => 0,                     # DateTime when statistics are dumped
78
    upTime => 0,                        # Seconds since program start
79
    Cycle => 0,                         # Major process loop counter
80
    phase => 'Init',                    # Current phase of operation
81
    state => 'OK',                      # Nagios state
82
    wedged => 0,                        # Wedge indication - main loop not cycling
83
    slowGen => 0,                       # Requests that exceed max time
84
                                        # 
85
                                        # The following are reset each day
86
    dayStart => 0,                      # DateTime when daily data was reset
87
    txCount => 0,                       # Packages Transferred
88
    txBytes => 0,                       # Bytes Transferred
89
    linkErrors => 0,                    # Transfer (S3) errors
7485 dpurdie 90
    dbErrors => 0,                      # Database errors
7469 dpurdie 91
                                        # 
92
                                        # Per Cycle Data - Calculated each processing Cycle
93
    total => 0,                         # Number targets
94
);
95
 
96
#
97
#   Describe configuration parameters
98
#
99
my %cdata = (
100
    'piddir'          => {'mandatory' => 1      , 'fmt' => 'dir'},
101
    'sleep'           => {'default'   => 5      , 'fmt' => 'period'},
102
    'sleepLinkDown'   => {'default'   => '1m'   , 'fmt' => 'period'},
103
    'dpkg_archive'    => {'mandatory' => 1      , 'fmt' => 'dir'},
104
    'logfile'         => {'mandatory' => 1      , 'fmt' => 'vfile'},
105
    'logfile.size'    => {'default'   => '1M'   , 'fmt' => 'size'},
106
    'logfile.count'   => {'default'   => 9      , 'fmt' => 'int'},
107
    'wedgeTime'       => {'default'   => '30m'  , 'fmt' => 'period'},
108
    'waitTime'        => {'default'   => '60m'  , 'fmt' => 'period'},
109
 
110
    'verbose'         => {'default'   => 0      , 'fmt' => 'int'},                  # Debug ...
111
    'active'          => {'default'   => 1      , 'fmt' => 'bool'},                 # Disable alltogether
112
    'debug'           => {'default'   => 0      , 'fmt' => 'bool'},                 # Log to screen
113
    'txdetail'        => {'default'   => 0      , 'fmt' => 'bool'},                 # Show transfer times
114
    'noTransfers'     => {'default'   => 0      , 'fmt' => 'bool'},                 # Debugging option to prevent transfers
115
 
116
    'tagdir'          => {'mandatory' => 1      , 'fmt' => 'mkdir'},
117
    'workdir'         => {'mandatory' => 1      , 'fmt' => 'mkdir'},
118
    'forcedirscan'    => {'default'   => 100    , 'fmt' => 'period'},
119
    'forces3update'   => {'default'   => '30m'  , 'fmt' => 'period'},
120
    'tagListUpdate'   => {'default'   => '1h'   , 'fmt' => 'period'},
121
    'S3Bucket'        => {'mandatory' => 1      , 'fmt' => 'text'},
122
    'S3Profile'       => {'mandatory' => 1      , 'fmt' => 'text'},
123
    'S3Region'        => {'default' => undef    , 'fmt' => 'text'},
124
 
125
    'RM_USERNAME_RW'  => {'mandatory' => 1      , 'fmt' => 'text'},
126
    'RM_PASSWORD_RW'  => {'mandatory' => 1      , 'fmt' => 'text'},
127
    'RM_LOCATION'     => {'default' => undef    , 'fmt' => 'text'},
128
 
129
 
130
);
131
 
132
 
133
#
134
#   Read in the configuration
135
#       Set up a logger
136
#       Write a pidfile - thats not used
137
$now = $startTime = time();
138
readConfig();
139
Utils::writepid($conf);
140
$logger->logmsg("Starting...");
141
readStatistics();
142
sighandlers();
143
 
144
#
145
#   Main processing loop
146
#   Will exit when terminated by parent
147
#
148
while (1)
149
{
150
    $logger->verbose3("Processing");
151
    $statistics{Cycle}++;
152
    $now = time();
153
    Utils::resetWedge();
154
 
155
    $statistics{phase} = 'ReadConfig';
156
    readConfig();
157
    if ( $conf->{'active'} )
158
    {
159
        $statistics{phase} = 'Refresh S3 Info';
160
        refreshS3Info();
161
        if( $linkUp )
162
        {
163
            $statistics{phase} = 'Monitor Requests';
164
            monitorRequests();
165
 
166
            $statistics{phase} = 'maintainTagList';
167
            maintainTagList();
168
        }
169
    }
170
 
171
    $statistics{phase} = 'Sleep';
7485 dpurdie 172
    sleep( ($linkUp && $dbUp) ? $conf->{'sleep'} : $conf->{'sleepLinkDown'} );
7469 dpurdie 173
    reapChildren();
174
 
175
    #   If my PID file ceases to be, then exit the daemon
176
    #   Used to force daemon to restart
177
    #
178
    unless ( -f $conf->{'pidfile'} )
179
    {
180
        $logger->logmsg("Terminate. Pid file removed");
181
        last;
182
    }
183
}
184
$statistics{phase} = 'Terminated';
185
$logger->logmsg("Child End");
186
exit 0;
187
 
188
#-------------------------------------------------------------------------------
189
# Function        : reapChildren 
190
#
191
# Description     : Reap any and all dead children
192
#                   Call in major loops to prevent zombies accumulating 
193
#
194
# Inputs          : None
195
#
196
# Returns         : 
197
#
198
sub reapChildren
199
{
200
    my $currentPhase = $statistics{phase};
201
    $statistics{phase} = 'Reaping';
202
 
203
    my $kid;
204
    do {
205
        $kid = waitpid(-1, WNOHANG);
206
    } while ( $kid > 0 );
207
 
208
    $statistics{phase} = $currentPhase;
209
}
210
 
211
 
212
#-------------------------------------------------------------------------------
213
# Function        : readConfig
214
#
215
# Description     : Re read the config file if it modification time has changed
216
#
217
# Inputs          : Nothing
218
#
219
# Returns         : 0       - Config not read
220
#                   1       - Config read
221
#                             Config file has changed
222
#
223
sub readConfig
224
{
225
    my ($mtime) = Utils::mtime($ARGV[0]);
226
    my $rv = 0;
227
 
228
    if ( $mtimeConfig != $mtime )
229
    {
230
        $logger->logmsg("Reading config file: $ARGV[0]");
231
        $mtimeConfig = $mtime;
232
        my $errors;
233
        ($conf, $errors) = Utils::readconf ( $ARGV[0], \%cdata );
234
        if ( scalar @{$errors} > 0 )
235
        {
236
            warn "$_\n" foreach (@{$errors});
237
            die ("Config contained errors\n");
238
        }
239
 
240
        #
241
        #   Reset some information
242
        #   Create a new logger
243
        #
244
        $logger = Logger->new($conf) unless $conf->{debug};
245
        $conf->{logger} = $logger;
246
        $conf->{'pidfile'} = $conf->{'piddir'} . '/' . $name . '.pid';
247
        $logger->setVerbose($conf->{verbose});
248
        $logger->verbose("Log Levl: $conf->{verbose}");
249
 
250
        #
251
        #   Setup statistics filename
252
        $conf->{'statsfile'} = $conf->{'piddir'} . '/' . $name . '.stats';
253
        $conf->{'statsfiletmp'} = $conf->{'piddir'} . '/' . $name . '.stats.tmp';
254
 
255
        #
256
        #   When config is read force some actions
257
        #       - Force tagList to be created
258
        #       - Force refresh from S3
259
        $lastTagListUpdate = 0;
260
        $lastS3Refresh = 0;
261
        $rv = 1;
262
 
263
        #
264
        #   When config is read force some actions
265
#Utils::DebugDumpData ("Config", $conf);
266
 
267
        $logger->warn("All Transfers disabled") if ( $conf->{'noTransfers'} );
268
        $logger->warn("s3Manifest is inactive") unless ( $conf->{'active'} );
269
    }
270
 
271
    return $rv;
272
}
273
 
274
#-------------------------------------------------------------------------------
275
# Function        : refreshS3Info 
276
#
277
# Description     : At startup, and at time after startup examine the S3 bucket
278
#                   and recover information from it 
279
#
280
# Inputs          : 
281
#
282
# Returns         : 0 - Gross error ( Bucket access) 
283
#
284
sub refreshS3Info
285
{
286
    my $rv = 1;
287
    if ( !$linkUp || ($now > ($lastS3Refresh + $conf->{'forces3update'})) )
288
    {
289
        $logger->verbose("refreshS3Info");
290
        $lastS3Refresh = $now;
291
 
292
        #
293
        #   Examine the s3 bucket and extract useful information
294
        #
295
        my $startTime = time;
296
        $rv =  examineS3Bucket();
297
         unless ($rv) {
298
            $statistics{linkErrors}++;
299
            $linkUp = 0;
300
         } else {
301
             $linkUp = 1;
302
         }
303
 
304
         #
305
         #   Display the duration of the refresh
306
         #       Diagnostic use
307
         #
308
         if ($conf->{txdetail}) {
309
             my $duration = time - $startTime;
310
             $logger->logmsg("refreshS3Info: Stats: $duration Secs");
311
         }
312
 
313
    }
314
    return $rv;
315
}
316
 
317
#-------------------------------------------------------------------------------
318
# Function        : monitorRequests
319
#
320
# Description     : Monitor s3Manifest requests
321
#                   This is simply done my polling Release Manager - at the moment
322
#
323
# Inputs          : None
324
#
325
# Returns         : Nothing
326
#
327
sub monitorRequests
328
{
329
    #
330
    #   Determine if new tags are present by examining the time
331
    #   that the directory was last modified.
332
    #
333
    #   Allow for a forced scan to catch packages that did not transfer
334
    #   on the first attempt
335
    #
336
    my ($mtime) = Utils::mtime($conf->{'tagdir'} );
337
    if ( ($mtime > $tagDirTime) || ($now > ($lastDirScan + $conf->{'forcedirscan'})) )
338
    {
339
        $logger->verbose2("monitorRequests: $conf->{'tagdir'}");
340
        #$logger->verbose2("monitorRequests: mtime:" . ($mtime > $tagDirTime));
341
        #$logger->verbose2("monitorRequests: last:" . ($now > ($lastDirScan + $conf->{'forcedirscan'})));
342
 
343
        #
344
        #   Package tags information is not really used
345
        #       Just delete all the tags
346
        #       Used to trigger the scan - rather than rely on the slow data
347
        #       base poll. Still need a change in release sequence number
348
        #   
349
        my $dh;
350
        unless (opendir($dh, $conf->{'tagdir'}))
351
        {
352
            $logger->warn ("can't opendir $conf->{'tagdir'}: $!");
353
            return;
354
        }
355
 
356
        #
357
        #   Process each entry
358
        #       Ignore those that start with a .
359
        #       Remove all files
360
        #
361
        while (my $tag = readdir($dh) )
362
        {
363
            next if ( $tag =~ m~^\.~ );
364
            my $file = "$conf->{'tagdir'}/$tag";
365
            $logger->verbose3("processTags: $file");
366
 
367
            next unless ( -f $file );
368
            unlink $file;
369
        }
370
 
371
        #
372
        #   Reset the scan time triggers
373
        #   
374
        $tagDirTime = $mtime;
375
        $lastDirScan = $now;
376
 
377
        #
378
        #   Examine Release Manager looking for snapshot releases that have s3Manifest support that have not
379
        #   been satisfied.
380
        #
381
        $ENV{GBE_RM_USERNAME} = $conf->{RM_USERNAME_RW} ;
382
        $ENV{GBE_RM_PASSWORD} = $conf->{RM_PASSWORD_RW} ;
383
        $ENV{GBE_RM_LOCATION} = $conf->{RM_LOCATION} if defined $conf->{RM_LOCATION};
384
 
385
        connectRM(\$RM_DB, $conf->{verbose} > 3);
386
 
387
        my $m_sqlstr = "SELECT rtag_id, parent_rtag_id, s3manifest, s3manifest_done " .
7485 dpurdie 388
                       "FROM release_manager.release_tags rt " .
7469 dpurdie 389
                       "WHERE rt.OFFICIAL = 'S' " .
390
                       "      AND rt.S3MANIFEST = 'Y' " .
391
                       "      AND rt.S3MANIFEST_DONE != 'Y' " .
392
                       "ORDER BY rtag_id ";
7485 dpurdie 393
        my $stuckEntry = 0;
394
        my $curData = getDataFromRm ('monitorRequests', $m_sqlstr, {data => 0, sql => 0, dump => 0} );
395
        $dbUp = !$RMerror;
7469 dpurdie 396
 
7485 dpurdie 397
        if ($curData) {
398
            $statistics{total} = scalar @{$curData};
7469 dpurdie 399
 
7485 dpurdie 400
            # Add new enties to the set to process
401
            foreach my $entry (@{$curData}) {
402
                my ($rtag_id, $parent_rtag_id) = @{$entry};
403
                unless ( exists $activeReleases->{$rtag_id}) {
404
                    $logger->logmsg("New Release Detected. rtag_id: $rtag_id, parent_rtag_id:$parent_rtag_id");
405
                    $activeReleases->{$rtag_id}{parent_rtag_id} = $parent_rtag_id;
406
                    $activeReleases->{$rtag_id}{rtag_id} = $rtag_id;
407
                    $activeReleases->{$rtag_id}{startTime} = $now;
408
                    $lastTagListUpdate = 0;
409
                }
7469 dpurdie 410
            }
411
 
7485 dpurdie 412
            # Process all the entries
413
            foreach my $rtag_id ( sort keys %{$activeReleases} ) {
414
                processSnapshot($rtag_id);
7469 dpurdie 415
 
7485 dpurdie 416
                # Warn if stuck entry
417
                # Repeat the warning on periodic basis
418
                if (exists $activeReleases->{$rtag_id}) {
419
                    my $entry = $activeReleases->{$rtag_id};
420
 
421
                    if ($now - $activeReleases->{$rtag_id}{startTime} > $conf->{waitTime}) {
422
                        $logger->warn("Max waitTime exceeded: rtag_id: $rtag_id, parent_rtag_id:$entry->{parent_rtag_id}");
423
                        $activeReleases->{$rtag_id}{startTime} = $now;
424
                        $entry->{isStuck} = 1;
425
                    }
7469 dpurdie 426
 
7485 dpurdie 427
                    if ($entry->{isStuck}) {
428
                        $stuckEntry++;
429
                    }
430
 
7469 dpurdie 431
                }
432
            }
433
        }
434
        $statistics{slowGen} = $stuckEntry; 
435
        disconnectRM(\$RM_DB);
436
    }
437
}
438
 
439
#-------------------------------------------------------------------------------
440
# Function        : examineS3Bucket 
441
#
442
# Description     : Scan the S3 bucket
443
#                   Currently only validates that the bucket exist
444
#                   and that the link is up.     
445
#                       
446
# Inputs          : Nothing 
447
#
448
# Returns         : 0 - Gross error ( Bucket access) 
449
#
450
sub examineS3Bucket
451
{
452
    my $bucket;
453
    my $prefix;
454
 
455
    if ($conf->{'S3Bucket'} =~ m~(.*?)/(.*)~) {
456
        $bucket = $1;
457
        $prefix = $2;
458
    } else {
459
        $bucket = $conf->{'S3Bucket'};
460
    }
461
 
462
    my $s3_cmd = "aws --profile $conf->{'S3Profile'} --output json";
463
    $s3_cmd .= " --region $conf->{'S3Region'}" if (defined $conf->{'S3Region'});
464
    $s3_cmd .= " s3api head-bucket --bucket $bucket";
465
 
466
    $logger->verbose2("examineS3Bucket:s3_cmd:$s3_cmd");
467
 
468
    my $ph;
469
    my $jsontxt = "";
470
    open ($ph, "$s3_cmd 2>&1 |");
471
    while ( <$ph> ) {
472
        chomp;
473
        $logger->verbose3("examineS3Bucket:Data: $_");
474
    }
475
    close ($ph);
476
    my $cmdRv = $?;
477
    if ($cmdRv != 0) {
478
        $logger->warn("Cannot read S3 Bucket Data");
479
        return 0;
480
    }
481
 
482
#Utils::DebugDumpData("activeReleases",$activeReleases);
483
    return 1;
484
}
485
 
486
#-------------------------------------------------------------------------------
487
# Function        : processSnapshot 
488
#
489
# Description     : Process a snaphot entry
490
#                   Ensure all dependent packages are in the master S3 pkg_archive bucket
491
#                   before creating the actual manifest 
492
#
493
# Inputs          : $rtagId
494
#
495
# Returns         : Nothing 
496
#
497
sub processSnapshot
498
{
499
    my ($rtagId) = @_;
500
    my $data = $activeReleases->{$rtagId};
501
    return unless defined $data;
502
    $logger->logmsg("Process: $rtagId $data->{parent_rtag_id}");
503
 
504
#Utils::DebugDumpData("Data", $data);
505
 
506
    unless (exists $data->{depList}) {
507
        $logger->verbose2("processSnapshot:GetDeps: $rtagId");
508
 
509
        #   Looks like a new entry - get the packages flagged for the manifest
510
        #   Determine all the package version that should go into the manifest
511
        #
512
        my $m_sqlstr = "SELECT rc.pv_id, p.PKG_NAME, pv.PKG_VERSION, pv.v_ext, 0" .
7485 dpurdie 513
                        " FROM release_manager.RELEASE_CONTENT rc, release_manager.PACKAGE_VERSIONS pv, release_manager.PACKAGES p " .
7469 dpurdie 514
                        " WHERE rc.RTAG_ID = $rtagId " .
515
                        "  AND rc.IN_MANIFEST = 'Y' " .
516
                        "  AND rc.pv_id = pv.pv_id " .
517
                        "  AND p.pkg_id = pv.pkg_id";
518
 
519
        #
520
        #   depList is an array:
521
        #       pv_id, pkg_name, pkg_version, v_ext, found
522
        $data->{depList} = getDataFromRm ('processSnapshot', $m_sqlstr, {data => 0} );
523
    }
524
 
525
    #
526
    #   Iterate over all the required dependencies and check that they exist
527
    #       I don't know a way of doing it all in one request
528
    #
529
    my $allFound = 1;       
530
    foreach my $entry ( @{$data->{depList}} )
531
    {
532
#        Utils::DebugDumpData("Entry", $entry);
533
        unless ($entry->[4]) {
534
            my $pe = checkPackageVersion($entry->[1], $entry->[2]);
535
            if ($pe) {
536
                $entry->[4] = 1;
537
                $logger->logmsg("Found: $entry->[1], $entry->[2]");
538
 
539
            } else {
540
                $allFound = 0;
541
                $logger->logmsg("Wait for: $entry->[1], $entry->[2]");
542
            }
543
        }
544
    }
545
 
546
    #   Have scannend all the dependencies
547
    #   If not all have been found, then we need to wait for them to arrive in the pkg_archive
548
    #   
549
    unless ($allFound) {
550
        $logger->verbose2("processSnapshot:Not all found: $rtagId");
551
        return;
552
    }
553
    $logger->verbose2("processSnapshot:All found: $rtagId");
554
 
555
    #
556
    #   Generate the manifest as a hash
557
    #   
558
    my $manifest;
559
    $manifest->{rtag_id} = $data->{parent_rtag_id}; 
560
    $manifest->{formatVersion} = 1;
561
    $manifest->{snapshot} = $rtagId;
562
    $manifest->{deployed} = [];
563
    foreach my $entry ( @{$data->{depList}} ) {
564
        my $depEntry;
565
        $depEntry->{package} = $entry->[1];
566
        $depEntry->{version} = $entry->[2];
567
        $depEntry->{alias} = $entry->[1] . $entry->[3];
568
        $depEntry->{pv_id} = $entry->[0];
7485 dpurdie 569
        $depEntry->{stored} = $entry->[1] . '__' . $entry->[2] . '.tgz';
7469 dpurdie 570
        push @{$manifest->{deployed}}, $depEntry;
571
    }
572
 
573
    my $jsonText = to_json( $manifest, { ascii => 1, pretty => 1, canonical => 1 });
574
 
575
 
576
#Utils::DebugDumpData("Manifest", $manifest);
577
    $logger->verbose2("processSnapshot:Manifest: $jsonText");
578
    my $targetName = 'manifest_' . $manifest->{rtag_id} . '_' . $manifest->{snapshot} . '.json';
579
    my $file = catdir( $conf->{'workdir'} , $targetName);
580
    $logger->verbose2("processSnapshot:ManifestFile: $file");
581
    unlink $file;
582
 
583
    if (open ( my $fh, '>', $file) ) {
584
        print $fh $jsonText;
585
        close $fh
586
    } else {
587
        $logger->warn("Can't write file: $file. $!");
588
        return;
589
    }
590
 
591
    #
592
    #   Transfer to the S3 bucket
593
    #   
594
    #   Create a command to transfer the file to AWS use the cli tools
595
    #
596
    $startTime = time;
597
    my $targetPath = catdir ($conf->{'S3Bucket'}, $targetName );
598
 
599
    my $s3_cmd = "aws --profile $conf->{'S3Profile'} --output json";
600
    $s3_cmd .= " --region $conf->{'S3Region'}" if (defined $conf->{'S3Region'});
7485 dpurdie 601
    $s3_cmd .= " s3 cp --quiet $file s3://$targetPath";
7469 dpurdie 602
 
603
    $logger->logmsg("transferManifest:$targetPath");
604
    $logger->verbose2("transferManifest:s3_cmd:$s3_cmd");
605
 
606
    my $cmdRv;
607
    unless ($conf->{'noTransfers'}) {
608
        my $ph;
7485 dpurdie 609
        open ($ph, "$s3_cmd 2>&1 |");
7469 dpurdie 610
        while ( <$ph> )
611
        {
612
            chomp;
613
            $logger->verbose2("transferManifest:Data: $_");
614
        }
615
        close ($ph);
616
        $cmdRv = $?;
617
        $logger->verbose("transferManifest:End: $cmdRv");
618
    }
619
 
620
    if ($cmdRv != 0) {
621
        $logger->warn("transferManifest:Error:$targetPath");
622
        return;
623
    }
624
 
625
    #
626
    #   Display the size of the package
627
    #       Diagnostic use
628
    #
629
    if ($conf->{txdetail}) {
630
        my $tzfsize = -s $file;
631
        my $duration = time - $startTime;
632
        $logger->logmsg("S3 Copy: Stats: $targetName, $tzfsize Bytes, $duration Secs");
633
    }
634
 
635
    $statistics{txCount}++;
636
    $statistics{txBytes} += -s $file;
637
 
638
    #
639
    #   Mark the Release Manager entry as done
640
    #       Need Write access to do this
641
    #
642
    $logger->verbose("Update database: $rtagId");
7485 dpurdie 643
    my $rv = executeRmQuery('MarkDone', "UPDATE release_manager.RELEASE_TAGS SET S3MANIFEST_DONE = 'Y' where RTAG_ID = $rtagId") ;
7469 dpurdie 644
    if ($rv) {
645
        return;
646
    }
647
 
648
    #
649
    #   Remove the entry from hash of items to be processed
650
    #
651
    $logger->logmsg("Complete: $targetName");
652
    delete $activeReleases->{$rtagId};
653
    unlink $file;
7485 dpurdie 654
    $lastTagListUpdate = 0;
7469 dpurdie 655
    return;
656
}
657
 
658
#-------------------------------------------------------------------------------
659
# Function        : checkPackageVersion 
660
#
661
# Description     : Check that a specified package-versions exists in the dpkg_archive
662
#                   S3 bucket
663
#
664
# Inputs          : $pname
665
#                   $pversion
666
#
667
# Returns         : Ref to metadata
668
#                   undef on not found 
669
#
670
 
671
sub checkPackageVersion
672
{
673
    my ($pname, $pversion) = @_;
674
 
675
    my $objKey = $pname . '__' . $pversion  . '.tgz';
676
 
677
    my $s3_cmd = "aws --profile $conf->{'S3Profile'} --output json";
678
    $s3_cmd .= " s3api head-object --bucket vix-dpkg-archive --key $objKey";
679
 
680
    $logger->verbose2("checkPackageVersion:s3_cmd:$s3_cmd");
681
 
682
    my $ph;
683
    my $jsontxt = "";
684
    if (open ($ph, "$s3_cmd 2>/dev/null |") )
685
    {
686
        while ( <$ph> ) {
687
            chomp;
688
            $logger->verbose2("checkPackageVersion:Data: $_");
689
            $jsontxt .= $_;
690
        }
691
        close ($ph);
692
    }
693
    if ($jsontxt) {
694
        my $json = from_json ($jsontxt);
695
#        Utils::DebugDumpData("JSON",$json);
696
        return $json;
697
    }
698
    else {
699
        $logger->verbose2("checkPackageVersion: $pname, $pversion Not Found");
700
        return undef;
701
    }
702
}
703
 
704
 
705
#-------------------------------------------------------------------------------
706
# Function        : getDataFromRm 
707
#
708
# Description     : Get an array of data from RM
709
#                   Normally an array of arrays 
710
#
711
# Inputs          : $name           - Query Name
712
#                   $m_sqlstr       - Query
713
#                   $options        - Ref to a hash of options
714
#                                       sql     - show sql
715
#                                       data    - show data
716
#                                       dump    - show results
717
#                                       oneRow  - Only fetch one row
718
#                                       error   - Must find data
719
#                                       
720
# Returns         : ref to array of data
721
#
722
sub getDataFromRm
723
{
724
    my ($name,$m_sqlstr, $options ) = @_;
725
    my @row;
726
    my $data;
7485 dpurdie 727
    $RMerror = 0;
7469 dpurdie 728
 
729
    if (ref $options ne 'HASH') {
730
        $options = {}; 
731
    }
732
 
733
    if ($options->{sql}) {
734
        $logger->logmsg("$name: $m_sqlstr")
735
    }
736
    my $sth = $RM_DB->prepare($m_sqlstr);
737
    if ( defined($sth) )
738
    {
739
        if ( $sth->execute( ) ) {
740
            if ( $sth->rows ) {
741
                while ( @row = $sth->fetchrow_array ) {
742
                    if ($options->{data}) {
743
                        $logger->warn ("$name: @row");
744
                    }
745
                    #Debug0("$name: @row");
746
                    push @{$data}, [@row];
747
 
748
                    last if $options->{oneRow};
749
                }
750
            }
751
            $sth->finish();
752
        } else {
753
            $logger->warn("Execute failure:$name: $m_sqlstr", $sth->errstr() );
7485 dpurdie 754
            $RMerror++;
755
            $statistics{dbErrors}++;
7469 dpurdie 756
        }
757
    } else {
758
        $logger->warn("Prepare failure:$name" );
7485 dpurdie 759
        $RMerror++;
760
        $statistics{dbErrors}++;
7469 dpurdie 761
    }
762
 
763
    if (!$data && $options->{error}) {
764
        $logger->warn( $options->{error} );
765
    }
766
 
767
    if ($data && $options->{oneRow}) {
768
        $data = $data->[0];
769
    }
770
 
771
    if ($options->{dump}) {
772
        Utils::DebugDumpData("$name", $data);
773
    }
774
    return $data;
775
}
776
 
777
#-------------------------------------------------------------------------------
778
# Function        : executeRmQuery 
779
#
780
# Description     : Execute a simple RM query. One that does not expect any return data
781
#                   Assume DB connection has been established    
782
#
783
# Inputs          : $fname           - OprName, for error reporting
784
#                   $m_sqlstr        - SQL String
785
#
786
# Returns         : 1 - on Error
787
#                   0 - All good
788
#               
789
#
790
sub executeRmQuery
791
{
792
    my ($fname, $m_sqlstr) = @_;
793
 
794
    $logger->verbose3('ExecuteQuery:', $fname);
7485 dpurdie 795
    $RMerror = 0;
7469 dpurdie 796
    #
797
    #   Create the full SQL statement
798
    #
799
    my $sth = $RM_DB->prepare($m_sqlstr);
800
    if ( defined($sth) )
801
    {
802
        if ( $sth->execute() )
803
        {
804
            $sth->finish();
805
        }
806
        else
807
        {
808
            $logger->warn("$fname: Execute failure: $m_sqlstr", $sth->errstr() );
7485 dpurdie 809
            $RMerror++;
810
            $statistics{dbErrors}++;
7469 dpurdie 811
            return 1;
812
        }
813
    }
814
    else
815
    {
816
        $logger->warn("$fname: Prepare failure");
7485 dpurdie 817
        $RMerror++;
818
        $statistics{dbErrors}++;
7469 dpurdie 819
        return 1;
820
    }
821
 
822
    return 0;
823
}
824
 
825
#-------------------------------------------------------------------------------
826
# Function        : maintainTagList
827
#
828
# Description     : Maintain a data structure for the maintenance of the
829
#                   tags directory
830
#
831
# Inputs          : None
832
#
833
# Returns         : Nothing
834
#
835
sub maintainTagList
836
{
837
    #
838
    #   Time to perform the scan
839
    #   Will do at startup and every time period there after
840
    #
841
    return unless ( $now > ($lastTagListUpdate + $conf->{tagListUpdate} ));
842
    $logger->verbose("maintainTagList");
843
    $lastTagListUpdate = $now;
844
 
845
    #
846
    #   Generate new configuration
847
    #
848
    my %config;
849
    $config{s3Manifest} = 1;                # Indicate that it may be special
850
 
851
    %{$config{releases}} = map { $_ => 1 } keys %{$activeReleases};
852
 
853
    #
854
    #   Save data
855
    #
856
    my $dump =  Data::Dumper->new([\%config], [qw(*config)]);
857
#print $dump->Dump;
858
#$dump->Reset;
859
 
860
    #
861
    #   Save config data
862
    #
863
    my $conf_file = catfile( $conf->{'tagdir'},'.config' );
864
    $logger->verbose3("maintainTagList: Writting $conf_file");
865
 
866
    my $fh;
867
    open ( $fh, '>', $conf_file ) or $logger->err("Can't create $conf_file: $!");
868
    print $fh $dump->Dump;
869
    close $fh;
870
}
871
 
872
#-------------------------------------------------------------------------------
873
# Function        : resetDailyStatistics 
874
#
875
# Description     : Called periodically to reset the daily statistics
876
#
877
# Inputs          : $time       - Current time
878
#
879
# Returns         : 
880
#
881
sub resetDailyStatistics
882
{
883
    my ($time) = @_;
884
 
885
    #
886
    #   Detect a new day
887
    #
888
    my $today = (localtime($time))[7];
889
    if ($yday != $today)
890
    {
891
        $yday = $today;
892
        $logger->logmsg('Resetting daily statistics' );
893
 
894
        # Note: Must match @recoverTags in readStatistics
895
        $statistics{dayStart} = $time;
896
        $statistics{txCount} = 0;
897
        $statistics{txBytes} = 0;
898
        $statistics{linkErrors} = 0;
7485 dpurdie 899
        $statistics{dbErrors} = 0;
7469 dpurdie 900
    }
901
}
902
 
903
#-------------------------------------------------------------------------------
904
# Function        : readStatistics 
905
#
906
# Description     : Read in the last set of stats
907
#                   Used after a restart to recover daily statistics
908
#
909
# Inputs          : 
910
#
911
# Returns         : 
912
#
913
sub readStatistics
914
{
7485 dpurdie 915
    my @recoverTags = qw(dayStart txCount txBytes linkErrors dbErrors);
7469 dpurdie 916
 
917
    if ($conf->{'statsfile'} and -f $conf->{'statsfile'})
918
    {
919
        if (open my $fh, $conf->{'statsfile'})
920
        {
921
            while (<$fh>)
922
            {
923
                m~(.*):(.*)~;
924
                if ( grep( /^$1$/, @recoverTags ) ) 
925
                {
926
                    $statistics{$1} = $2;
927
                    $logger->verbose("readStatistics $1, $2");
928
                }
929
            }
930
            close $fh;
931
            $yday = (localtime($statistics{dayStart}))[7];
932
        }
933
    }
934
}
935
 
936
 
937
#-------------------------------------------------------------------------------
938
# Function        : periodicStatistics 
939
#
940
# Description     : Called on a regular basis to write out statistics
941
#                   Used to feed information into Nagios
942
#                   
943
#                   This function is called via an alarm and may be outside the normal
944
#                   processing loop. Don't make assumptions on the value of $now
945
#
946
# Inputs          : 
947
#
948
# Returns         : 
949
#
950
sub periodicStatistics
951
{
952
    #
953
    #   A few local stats
954
    #
955
    $statistics{SeqNum}++;
956
    $statistics{timeStamp} = time();
957
    $statistics{upTime} = $statistics{timeStamp} - $startTime;
958
    $statistics{wedged} = Utils::isWedged($conf);
959
 
960
    if ( $statistics{wedged}) {
961
         $statistics{state} = 'Wedged';
7485 dpurdie 962
    } elsif(!$dbUp){
963
        $statistics{state} = 'RM Access error';
7469 dpurdie 964
    } elsif(!$linkUp){
965
        $statistics{state} = 'S3 Bucket Read Error';
966
    } elsif ($statistics{slowGen}) {
967
        $statistics{state} = 'Slow manifest generation detected';
968
    } else {
969
        $statistics{state} = 'OK';
970
    }
971
 
972
 
973
    #   Reset daily accumulations - on first use each day
974
    resetDailyStatistics($statistics{timeStamp});
975
 
976
    #
977
    #   Write statistics to a file
978
    #       Write to a tmp file, then rename.
979
    #       Attempt to make the operation atomic - so that the file consumer
980
    #       doesn't get a badly formed file.
981
    #   
982
    if ($conf->{'statsfiletmp'})
983
    {
984
        my $fh;
985
        unless (open ($fh, '>', $conf->{'statsfiletmp'}))
986
        {
987
            $fh = undef;
988
            $logger->warn("Cannot create temp stats file: $!");
989
        }
990
        else
991
        {
992
            foreach my $key ( sort { lc($a) cmp lc($b) } keys %statistics)
993
            {
994
                print $fh $key . ':' . $statistics{$key} . "\n";
995
                $logger->verbose2('Statistics:'. $key . ':' . $statistics{$key});
996
            }
997
            close $fh;
998
 
999
            # Rename temp to real file
1000
            rename  $conf->{'statsfiletmp'},  $conf->{'statsfile'} ;
1001
        }
1002
    }
1003
}
1004
 
1005
#-------------------------------------------------------------------------------
1006
# Function        : sighandlers
1007
#
1008
# Description     : Install signal handlers
1009
#
1010
# Inputs          : Uses gobals
1011
#
1012
# Returns         : Nothing
1013
#
1014
sub sighandlers
1015
{
1016
    $SIG{TERM} = sub {
1017
        # On shutdown
1018
        $logger->logmsg('Received SIGTERM. Shutting down....' );
1019
        unlink $conf->{'pidfile'} if (-f $conf->{'pidfile'});
1020
        exit 0;
1021
    };
1022
 
1023
    $SIG{HUP} = sub {
1024
        # On logrotate
1025
        $logger->logmsg('Received SIGHUP.');
1026
        $logger->rotatelog();
1027
    };
1028
 
1029
    $SIG{USR1} = sub {
1030
        # On Force Rescans
1031
        $logger->logmsg('Received SIGUSR1.');
1032
        $lastTagListUpdate = 0;
1033
        $lastS3Refresh = 0;
1034
    };
1035
 
1036
    alarm 60;
1037
    $SIG{ALRM} = sub {
1038
        # On Dump Statistics
1039
        $logger->verbose2('Received SIGUSR2.');
1040
        periodicStatistics();
1041
        alarm 60;
1042
    };
1043
 
1044
    $SIG{__WARN__} = sub { $logger->warn("@_") };
1045
    $SIG{__DIE__} = sub { $logger->err("@_") };
1046
}
1047
 
1048
 
1049
#-------------------------------------------------------------------------------
1050
# Function        : Error, Verbose, Warning
1051
#
1052
# Description     : Support for JatsRmApi
1053
#
1054
# Inputs          : Message
1055
#
1056
# Returns         : Nothing
1057
#
1058
sub Error
1059
{
1060
    $logger->err("@_");
1061
}
1062
 
1063
sub Verbose
1064
{
1065
    $logger->verbose2("@_");
1066
}
1067
 
1068
sub Warning
1069
{
1070
    $logger->warn("@_");
1071
}
1072
 
1073