Subversion Repositories DevTools

Rev

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