Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Initial commit

  • Loading branch information...
commit 74926b66a71a3739eb13abbf5055639334bfa7c7 0 parents
Dominik Schulz authored December 26, 2012
14  .gitignore
... ...
@@ -0,0 +1,14 @@
  1
+blib*
  2
+.build/
  3
+Makefile
  4
+Makefile.old
  5
+Build
  6
+Build.bat
  7
+_build*
  8
+pm_to_blib*
  9
+*.tar.gz
  10
+.lwpcookies
  11
+cover_db
  12
+pod2htm*.tmp
  13
+MTK-MYB-CNC-*
  14
+Debian_CPANTS.txt
4  Changes
... ...
@@ -0,0 +1,4 @@
  1
+Revision history for MTK-MYB-CNC
  2
+
  3
+0.19    2012-12-26
  4
+        First public version, released on an unsuspecting world.
30  README.md
Source Rendered
... ...
@@ -0,0 +1,30 @@
  1
+This is the README file for MTK-MYB-CNC,
  2
+MTK-MYB CNC implementation.
  3
+
  4
+## Description
  5
+
  6
+MTK-MYB-CNC provides the MTK-MYB CNC implementation.
  7
+
  8
+## Installation
  9
+
  10
+This package uses Dist::Zilla.
  11
+
  12
+Use
  13
+
  14
+dzil build
  15
+
  16
+to create a release tarball which can be
  17
+unpacked and installed like any other EUMM
  18
+distribution.
  19
+
  20
+perl Makefile.PL
  21
+
  22
+make
  23
+
  24
+make test
  25
+
  26
+make install
  27
+
  28
+## Documentation
  29
+
  30
+Please see perldoc MTK::MYB::CNC.
44  dist.ini
... ...
@@ -0,0 +1,44 @@
  1
+name             = MTK-MYB-CNC
  2
+author           = Dominik Schulz <dominik.schulz@gauner.org>
  3
+license          = Perl_5
  4
+copyright_holder = Dominik Schulz
  5
+copyright_year   = 2012
  6
+version          = 0.19
  7
+
  8
+[GatherDir]
  9
+exclude_filename = README.pod
  10
+exclude_match    = ^doc.*
  11
+exclude_match    = ^cover.*
  12
+
  13
+[PruneCruft]
  14
+[Authority]
  15
+[PkgVersion]
  16
+[MetaJSON]
  17
+[ExtraTests]
  18
+[PodSyntaxTests]
  19
+[NoTabsTests]
  20
+[EOLTests]
  21
+
  22
+[Manifest]
  23
+[MakeMaker]
  24
+
  25
+[AutoPrereqs]
  26
+[Prereqs / BuildRequires]
  27
+[Prereqs]
  28
+[Prereqs / Recommends]
  29
+
  30
+[Git::CheckFor::CorrectBranch]
  31
+release_branch = master
  32
+
  33
+[@Git]
  34
+tag_format = version_%v
  35
+
  36
+[GitHub::Meta]
  37
+repo = MTK-MYB-CNC
  38
+
  39
+[PodWeaver]
  40
+
  41
+[MetaYAML]
  42
+[License]
  43
+[ReadmeFromPod]
  44
+
456  lib/MTK/MYB/CNC.pm
... ...
@@ -0,0 +1,456 @@
  1
+package MTK::MYB::CNC;
  2
+# ABSTRACT: MTK-MYB CNC implementation
  3
+
  4
+use 5.010_000;
  5
+use mro 'c3';
  6
+use feature ':5.10';
  7
+
  8
+use Moose;
  9
+use namespace::autoclean;
  10
+
  11
+# use IO::Handle;
  12
+# use autodie;
  13
+# use MooseX::Params::Validate;
  14
+
  15
+use Crypt::PasswdMD5 qw();
  16
+use Data::Dumper;
  17
+use Data::Pwgen;
  18
+use MTK::MYB::CNC::Job;
  19
+
  20
+extends 'MTK::MYB';
  21
+
  22
+has 'ftp_helper' => (
  23
+    'is'      => 'ro',
  24
+    'isa'     => 'Str',
  25
+    'default' => 'curl',
  26
+);
  27
+
  28
+has 'ftp_port' => (
  29
+    'is'      => 'ro',
  30
+    'isa'     => 'Int',
  31
+    'lazy'    => 1,
  32
+    'builder' => '_init_ftp_port',
  33
+);
  34
+
  35
+has 'ftp_uid' => (
  36
+    'is'      => 'ro',
  37
+    'isa'     => 'Int',
  38
+    'lazy'    => 1,
  39
+    'builder' => '_init_ftp_uid',
  40
+);
  41
+
  42
+has 'ftp_gid' => (
  43
+    'is'      => 'ro',
  44
+    'isa'     => 'Int',
  45
+    'lazy'    => 1,
  46
+    'builder' => '_init_ftp_gid',
  47
+);
  48
+
  49
+has 'tempdir' => (
  50
+    'is'      => 'ro',
  51
+    'isa'     => 'File::Temp::Dir',
  52
+    'lazy'    => 1,
  53
+    'builder' => '_init_tempdir',
  54
+);
  55
+
  56
+sub _plugin_base_class { return ('MTK::MYB::Plugin','MTK::MYB::CNC::Plugin') }
  57
+
  58
+sub _init_tempdir {
  59
+    my $self = shift;
  60
+
  61
+    my $Dir = File::Temp::->newdir();
  62
+
  63
+    if ( $self->debug() ) {
  64
+        $Dir->unlink_on_destroy(0);
  65
+    }
  66
+
  67
+    return $Dir;
  68
+}
  69
+
  70
+sub _init_ftp_uid {
  71
+    my $self = shift;
  72
+
  73
+    # Try different UIDs until we find one that exists on this host:
  74
+    # - ftp-uid should be configured in the config
  75
+    # - 34 is the user 'backup'
  76
+    # - 1 is the user 'daemon',
  77
+    # - 0 is the user 'root',
  78
+    my @uids = ( $self->config()->get('MTK::MYB::CNC::ftp-uid'), 34, 1, 0 );
  79
+    foreach my $uid (@uids) {
  80
+        if ( getpwuid($uid) ) {
  81
+            return $uid;
  82
+        }
  83
+    }
  84
+
  85
+    return 0;
  86
+}
  87
+
  88
+sub _init_ftp_gid {
  89
+    my $self = shift;
  90
+
  91
+    # Try differents GIDs until we find one that exists on this host:
  92
+    # - ftp-gid should be configured in the config
  93
+    # - 34 is the group 'backup',
  94
+    # - 1 is the group 'daemon',
  95
+    # - 0 is the group 'root',
  96
+    my @gids = ( $self->config()->get('MTK::MYB::CNC::ftp-gid'), 34, 1, 0 );
  97
+    foreach my $gid (@gids) {
  98
+        if ( getgrgid($gid) ) {
  99
+            return $gid;
  100
+        }
  101
+    }
  102
+
  103
+    return 0;
  104
+}
  105
+
  106
+sub type {
  107
+    return 'cnc';
  108
+}
  109
+
  110
+sub _prepare {
  111
+    my $self = shift;
  112
+
  113
+    $self->SUPER::_prepare()
  114
+      or return;
  115
+    $self->_stop_ftpd();
  116
+    $self->_start_ftpd();
  117
+    return 1;
  118
+}
  119
+
  120
+sub _cleanup {
  121
+    my $self = shift;
  122
+    my $ok   = shift;
  123
+
  124
+    if ( $self->debug() ) {
  125
+        $self->logger()->log( message => "DEBUG-MODE enabled. Sleeping for 180s before performing cleanup", level => 'debug', );
  126
+        sleep 180;
  127
+    }
  128
+
  129
+    $self->SUPER::_cleanup($ok);
  130
+    $self->_stop_ftpd();
  131
+
  132
+    return 1;
  133
+}
  134
+
  135
+sub _get_backup_host {
  136
+    my $self = shift;
  137
+    my $dbms = shift;
  138
+
  139
+    # host MUST NOT be taken from the default!
  140
+    return $self->config()->get( 'MTK::MYB::DBMS::' . $dbms . '::Hostname' );
  141
+}
  142
+
  143
+sub _get_job {
  144
+    my $self  = shift;
  145
+    my $vault = shift;
  146
+
  147
+    my $Job = MTK::MYB::CNC::Job::->new(
  148
+        {
  149
+            'parent' => $self,
  150
+            'vault'  => $vault,
  151
+            'logger' => $self->logger(),
  152
+            'config' => $self->config(),
  153
+            'bank'   => $self->bank(),
  154
+        }
  155
+    );
  156
+
  157
+    return $Job;
  158
+}
  159
+
  160
+sub _init_ftp_port {
  161
+    my $self = shift;
  162
+
  163
+    my $ftp_port = $self->config()->get('MTK::MYB::CNC::FtpPort') || 2334;
  164
+
  165
+    return $ftp_port;
  166
+}
  167
+
  168
+=head2 PURE-FTPD OPTIONS
  169
+
  170
+This section describes the pure-ftpd options use below.
  171
+
  172
+=over 4
  173
+
  174
+=item -d -d
  175
+
  176
+Turns on debug logging including responses.
  177
+
  178
+=item -S ,<PORT>
  179
+
  180
+Bind to the specified port. The colon is important!
  181
+
  182
+=item -f none
  183
+
  184
+Disable syslog logging. We use our own logfile for that.
  185
+
  186
+=item -j
  187
+
  188
+Auto-create any missing home directories.
  189
+
  190
+=item -z
  191
+
  192
+Allow access to hidden files.
  193
+
  194
+=item -B
  195
+
  196
+Daemonize
  197
+
  198
+=item -H
  199
+
  200
+Don't resolve host names.
  201
+
  202
+=item -4
  203
+
  204
+Use IPv4 only.
  205
+
  206
+=item -c 250
  207
+
  208
+Limit to 250 simultaneous clients.
  209
+
  210
+=item -k 98
  211
+
  212
+Disable uploads if the partition is more than 98% full.
  213
+
  214
+=item -Y 0
  215
+
  216
+Disable all TLS features.
  217
+
  218
+=item -O clf:<LOGFILE>
  219
+
  220
+Enable CLF-style logging to <LOGFILE>.
  221
+
  222
+=item -l puredb:<PDBFILE>
  223
+
  224
+Enable native PureDB authentication from <PDBFILE>.
  225
+
  226
+=back
  227
+
  228
+=cut
  229
+
  230
+sub _start_ftpd {
  231
+    my $self = shift;
  232
+
  233
+    my $cleanup = 1;
  234
+    if ( $self->debug() ) {
  235
+        $cleanup = 0;
  236
+    }
  237
+
  238
+    my $tempdir = $self->tempdir()->dirname();
  239
+
  240
+    my $passwdfile = $tempdir . '/pureftpd.passwd';
  241
+    if ( $self->_create_pdb($passwdfile) ) {
  242
+        $self->logger()->log( message => "Created password-db at $passwdfile", level => 'debug', );
  243
+    }
  244
+    else {
  245
+        $self->logger()->log( message => "Could not create password-db at $passwdfile", level => 'warning', );
  246
+        return;
  247
+    }
  248
+
  249
+    my $ftp_port = $self->ftp_port();
  250
+
  251
+    my $cmd = "/usr/sbin/pure-ftpd";
  252
+    if ( $self->debug() ) {
  253
+        $cmd .= " -d -d";
  254
+    }
  255
+    $cmd .= " -S ,$ftp_port -f none -j -z -B -H -4 -c 250 -k 98 -Y 0 -O clf:$tempdir/ftpd-transfer.log -l puredb:$tempdir/pureftpd.pdb";
  256
+
  257
+    if ( $self->sys()->run_cmd($cmd) ) {
  258
+        $self->logger()->log( message => "pure-ftpd started w/ cmd: $cmd", level => 'debug', );
  259
+        sleep 15 if $self->config()->get('MTK::MYB::Debug');
  260
+        return 1;
  261
+    }
  262
+    else {
  263
+        $self->logger()->log( message => "pure-ftpd failed to start w/ cmd: $cmd", level => 'warning', );
  264
+        sleep 15 if $self->config()->get('MTK::MYB::Debug');
  265
+        return;
  266
+    }
  267
+}
  268
+
  269
+#
  270
+sub _stash_xfer_log {
  271
+    my $self = shift;
  272
+
  273
+    my $tempdir = $self->tempdir()->dirname();
  274
+
  275
+    my $logfile = $tempdir . '/ftpd-transfer.log';
  276
+    if ( -e $logfile ) {
  277
+        my $target = '/var/log/mysqlbackup-cnc-xfer.log';
  278
+        my $cmd    = "cat $logfile >> $target";
  279
+        if ( $self->sys()->run_cmd($cmd) ) {
  280
+            $self->logger()->log( message => 'Saved pure-ftpd transfer log to ' . $target, level => 'debug', );
  281
+        }
  282
+        else {
  283
+            $self->logger()->log( message => 'Failed to save pure-ftpd transfer log to ' . $target, level => 'error', );
  284
+        }
  285
+    }
  286
+    else {
  287
+        $self->logger()->log( message => 'No pure-ftpd transfer log found at ' . $logfile, level => 'notice', );
  288
+    }
  289
+
  290
+    my $pdb = $tempdir . '/pureftpd.pdb';
  291
+    if ( $self->debug() && -e $pdb ) {
  292
+        my $target = '/var/log/mysqlbackup-cnc-pdb-' . time() . '.log';
  293
+        my $cmd    = "mv $pdb $target";
  294
+        if ( $self->sys()->run_cmd($cmd) ) {
  295
+            $self->logger()->log( message => 'Saved pure-ftpd pdb to ' . $target, level => 'debug', );
  296
+        }
  297
+        else {
  298
+            $self->logger()->log( message => 'Failed to save pure-ftpd pdb to ' . $target, level => 'debug', );
  299
+        }
  300
+    }
  301
+    else {
  302
+        $self->logger()->log( message => 'Not in debug mode or no pure-ftpd pdb found at ' . $pdb, level => 'debug', );
  303
+    }
  304
+
  305
+    return 1;
  306
+}
  307
+
  308
+sub _stop_ftpd {
  309
+    my $self = shift;
  310
+
  311
+    my ( $cmd, $pid );
  312
+
  313
+    my $ftp_port = $self->ftp_port();
  314
+
  315
+    $cmd = "netstat -nlp | grep pure-ftp | grep \":$ftp_port\" | grep -v grep | awk '{ print \$7; }' | cut -d'/' -f1";
  316
+    $self->logger()->log( message => "CMD: $cmd", level => 'debug', );
  317
+    $pid = $self->sys()->run_cmd( $cmd, { Chomp => 1, CaptureOutput => 1, } );
  318
+    if ( $pid && $pid =~ m/^\s*\d+\s*$/ ) {
  319
+        $cmd = "kill $pid >/dev/null 2>&1";
  320
+        $self->logger()->log( message => "CMD: $cmd", level => 'debug', );
  321
+        $self->sys()->run_cmd($cmd);
  322
+        $cmd = "kill -9 $pid >/dev/null 2>&1";
  323
+        $self->logger()->log( message => "CMD: $cmd", level => 'debug', );
  324
+        $self->sys()->run_cmd($cmd);
  325
+    }
  326
+    else {
  327
+        $self->logger()->log( message => "stop_ftpd - no valid pid found: $pid", level => 'notice', );
  328
+    }
  329
+
  330
+    $cmd = "ps aux | grep pure-ftp | grep -v grep | awk '{ print \$2; }'";
  331
+    $self->logger()->log( message => "CMD: $cmd", level => 'debug', );
  332
+    $self->sys()->run_cmd( $cmd, { Chomp => 1, CaptureOutput => 1, } );
  333
+
  334
+    $self->_stash_xfer_log();
  335
+
  336
+    if ( $pid && $pid =~ m/^\s*\d+\s*$/ ) {
  337
+        $cmd = "kill $pid >/dev/null 2>&1";
  338
+        $self->logger()->log( message => "CMD: $cmd", level => 'debug', );
  339
+        $self->sys()->run_cmd($cmd);
  340
+        $cmd = "kill -9 $pid >/dev/null 2>&1";
  341
+        $self->logger()->log( message => "CMD: $cmd", level => 'debug', );
  342
+        $self->sys()->run_cmd($cmd);
  343
+        return 1;
  344
+    }
  345
+    else {
  346
+        $self->logger()->log( message => "stop_ftpd - no valid pid found: $pid", level => 'notice', );
  347
+        return;
  348
+    }
  349
+}
  350
+
  351
+# create pure-db
  352
+sub _create_pdb {
  353
+    my $self       = shift;
  354
+    my $passwdfile = shift;
  355
+
  356
+    my $dbms_ref = $self->config()->get('MTK::MYB::DBMS');
  357
+
  358
+    if(!$dbms_ref || ref($dbms_ref) ne 'HASH') {
  359
+        $self->logger()->log( message => 'No DBMS found!', level => 'error', );
  360
+        return;
  361
+    }
  362
+
  363
+    # DGR: Well, this is brief enough ;)
  364
+    ## no critic (RequireBriefOpen)
  365
+    if ( open( my $FH, ">", $passwdfile ) ) {
  366
+        my $ftp_uid = $self->ftp_uid();
  367
+        my $ftp_gid = $self->ftp_gid();
  368
+
  369
+        # create a individual login for each instance
  370
+        foreach my $instance ( sort keys %{ $dbms_ref } ) {
  371
+            my $salt     = &Data::Pwgen::pwgen( 6,  'alphanum' );
  372
+            my $password = &Data::Pwgen::pwgen( 12, 'alphanum' );
  373
+            $self->config()->set( 'MTK::MYB::DBMS::' . $instance . '::Password', $password );
  374
+            my $crypt = Crypt::PasswdMD5::unix_md5_crypt( $password, $salt );
  375
+            my $datadir =
  376
+              $self->fs()->makedir( $self->fs()->filename( ( $self->bank(), $instance, 'daily', 'inprogress' ) ), { Uid => $ftp_uid, Gid => $ftp_gid } );
  377
+
  378
+            #my $entry = $instance . ':' . $crypt . ':' . $ftp_uid . ':' . $ftp_gid . '::' . $datadir . '/' . $instance . '::::::::::::' . "\n";
  379
+            my $entry = $instance . ':' . $crypt . ':' . $ftp_uid . ':' . $ftp_gid . '::' . $datadir . '::::::::::::' . "\n";
  380
+            if ( print $FH $entry ) {
  381
+                $self->logger()->log( message => "Passwd-Entry: $entry - l/p: $instance/$password", level => 'debug', );
  382
+            }
  383
+            else {
  384
+                $self->logger()->log( message => "Could not write Passwd-Entry $entry to passwd-file $passwdfile", level => 'warning', );
  385
+            }
  386
+        }
  387
+
  388
+        if ( !close($FH) ) {
  389
+            $self->logger()->log( message => "Could not close file $passwdfile", level => 'debug', );
  390
+        }
  391
+        ## use critic
  392
+
  393
+        my $pdbfile = $passwdfile;
  394
+        $pdbfile =~ s/\.passwd$/.pdb/;
  395
+        my $cmd = "/usr/bin/pure-pw mkdb $pdbfile -f $passwdfile";
  396
+
  397
+        if ( $self->sys()->run_cmd($cmd) ) {
  398
+            $self->logger()->log( message => "ok mkdb w/ CMD: $cmd", level => 'debug', );
  399
+            return 1;
  400
+        }
  401
+        else {
  402
+            $self->logger()->log( message => "failed mkdb w/ CMD: $cmd", level => 3 );
  403
+            return;
  404
+        }
  405
+    }
  406
+    else {
  407
+        $self->logger()->log( message => "Could not open passwdfile at $passwdfile for writing: $!", level => 'alert' );
  408
+        return;
  409
+    }
  410
+}
  411
+
  412
+no Moose;
  413
+__PACKAGE__->meta->make_immutable;
  414
+
  415
+1;
  416
+
  417
+__END__
  418
+
  419
+=head1 NAME
  420
+
  421
+MTK::MYB::CNC - CNC implementation of mysqlbackup
  422
+
  423
+=head1 RISKS
  424
+
  425
+This section is included to help users of this tool to assess the risk associated
  426
+with using this tool. The two main categories adressed are those created the idea
  427
+implemented and those created by bugs. There may be other risks as well.
  428
+
  429
+B<myb cnc> is mostly a read-only tool that will, however, lock your database server
  430
+for the duration of the backup. This will cause service interruptions as long as
  431
+you don't take precautions. Either point the script to an dedicated slave or
  432
+chose an idle time for running it.
  433
+
  434
+=head1 SEE ALSO
  435
+
  436
+L<MySQL Backup> may be better suited if you plan to back up only a small number
  437
+of hosts.
  438
+
  439
+L<Percona XtraBackup|http://www.percona.com/software/percona-xtrabackup> is an
  440
+advanced approach for backing up InnoDB and XtraDB tables. It does provide little
  441
+advantage in terms of MyISAM backups.
  442
+
  443
+L<Holland Backup|http://wiki.hollandbackup.org/> is an multi-db application
  444
+written in Python.
  445
+
  446
+=method type
  447
+
  448
+Always returns cnc.
  449
+
  450
+=head1 ACKNOWLEDGEMENT
  451
+
  452
+This module was originally developed for eGENTIC Systems. With approval from eGENTIC Systems,
  453
+this module was generalized and published, for which the authors would like to express their
  454
+gratitude.
  455
+
  456
+=cut
61  lib/MTK/MYB/CNC/Job.pm
... ...
@@ -0,0 +1,61 @@
  1
+package MTK::MYB::CNC::Job;
  2
+# ABSTRACT: MTK CNC MYB job
  3
+
  4
+use 5.010_000;
  5
+use mro 'c3';
  6
+use feature ':5.10';
  7
+
  8
+use Moose;
  9
+use namespace::autoclean;
  10
+
  11
+# use IO::Handle;
  12
+# use autodie;
  13
+# use MooseX::Params::Validate;
  14
+# use Carp;
  15
+# use English qw( -no_match_vars );
  16
+# use Try::Tiny;
  17
+
  18
+use MTK::MYB::CNC::Worker;
  19
+
  20
+extends 'MTK::MYB::Job';
  21
+
  22
+# has ...
  23
+# with ...
  24
+# initializers ...
  25
+
  26
+sub _init_worker {
  27
+    my $self = shift;
  28
+
  29
+    my $Worker = MTK::MYB::CNC::Worker::->new(
  30
+        {
  31
+            'config'  => $self->config(),
  32
+            'logger'  => $self->logger(),
  33
+            'parent'  => $self->parent(),
  34
+            'verbose' => $self->verbose(),
  35
+            'dry'     => $self->dry(),
  36
+            'bank'    => $self->bank(),
  37
+            'vault'   => $self->vault(),
  38
+        }
  39
+    );
  40
+
  41
+    return $Worker;
  42
+}
  43
+
  44
+no Moose;
  45
+__PACKAGE__->meta->make_immutable;
  46
+
  47
+1;
  48
+
  49
+__END__
  50
+
  51
+=head1 NAME
  52
+
  53
+MTK::MYB::CNC::Job - MTK CNC MYB job
  54
+
  55
+=head1 ACKNOWLEDGEMENT
  56
+
  57
+This module was originally developed for eGENTIC Systems. With approval from eGENTIC Systems,
  58
+this module was generalized and published, for which the authors would like to express their
  59
+gratitude.
  60
+
  61
+=cut
43  lib/MTK/MYB/CNC/Plugin.pm
... ...
@@ -0,0 +1,43 @@
  1
+package MTK::MYB::CNC::Plugin;
  2
+# ABSTRACT: baseclass for any MYB CNC plugin
  3
+
  4
+use 5.010_000;
  5
+use mro 'c3';
  6
+use feature ':5.10';
  7
+
  8
+use Moose;
  9
+use namespace::autoclean;
  10
+
  11
+# use IO::Handle;
  12
+# use autodie;
  13
+# use MooseX::Params::Validate;
  14
+# use Carp;
  15
+# use English qw( -no_match_vars );
  16
+# use Try::Tiny;
  17
+
  18
+# extends ...
  19
+extends 'MTK::MYB::Plugin';
  20
+# has ...
  21
+# with ...
  22
+# initializers ...
  23
+
  24
+# your code here ...
  25
+
  26
+no Moose;
  27
+__PACKAGE__->meta->make_immutable;
  28
+
  29
+1;
  30
+
  31
+__END__
  32
+
  33
+=head1 NAME
  34
+
  35
+MTK::MYB::CNC::Plugin - baseclass for any MYB CNC plugin
  36
+
  37
+=head1 ACKNOWLEDGEMENT
  38
+
  39
+This module was originally developed for eGENTIC Systems. With approval from eGENTIC Systems,
  40
+this module was generalized and published, for which the authors would like to express their
  41
+gratitude.
  42
+
  43
+=cut
452  lib/MTK/MYB/CNC/Worker.pm
... ...
@@ -0,0 +1,452 @@
  1
+package MTK::MYB::CNC::Worker;
  2
+# ABSTRACT: CNC MYB worker
  3
+
  4
+use 5.010_000;
  5
+use mro 'c3';
  6
+use feature ':5.10';
  7
+
  8
+use Moose;
  9
+use namespace::autoclean;
  10
+
  11
+# use IO::Handle;
  12
+# use autodie;
  13
+# use MooseX::Params::Validate;
  14
+
  15
+use MTK::MYB::Codes;
  16
+
  17
+extends 'MTK::MYB::Worker';
  18
+
  19
+has 'curl_exitcodes' => (
  20
+    'is'      => 'ro',
  21
+    'isa'     => 'HashRef',
  22
+    'lazy'    => 1,
  23
+    'builder' => '_init_curl_exitcodes',
  24
+);
  25
+
  26
+sub _init_curl_exitcodes {
  27
+    my $self = shift;
  28
+
  29
+    my %codes = (
  30
+        0 => 'OK',
  31
+        1 => 'Unsupported protocol. This build of curl has no support for this protocol.',
  32
+        2 => 'Failed to initialize.',
  33
+        3 => 'URL malformed. The syntax was not correct.',
  34
+        5 => "Couldn't resolve proxy.  The  given  proxy  host  could  not  be resolved.",
  35
+        6 => "Couldn't resolve host. The given remote host was not resolved.",
  36
+        7 => 'Failed to connect to host.',
  37
+        8 => "FTP  weird  server  reply.  The  server  sent data curl couldn't parse.",
  38
+        9 =>
  39
+"FTP access denied. The server denied login or denied  access  to the  particular  resource or directory you wanted to reach. Most often you tried to change to a directory that doesn't  exist  on the server.",
  40
+        11 => "FTP  weird PASS reply. Curl couldn't parse the reply sent to the PASS request.",
  41
+        13 => "FTP weird PASV reply, Curl couldn't parse the reply sent to  the PASV request.",
  42
+        14 => "FTP  weird  227  format.  Curl  couldn't  parse the 227-line the server sent.",
  43
+        15 => "FTP can't get host. Couldn't resolve the host IP we got  in  the 227-line.",
  44
+        17 => "FTP  couldn't  set  binary.  Couldn't  change transfer method to binary.",
  45
+        18 => "Partial file. Only a part of the file was transferred.",
  46
+        19 => "FTP couldn't download/access the given file, the RETR (or  simi‐lar) command failed.",
  47
+        21 => "FTP quote error. A quote command returned error from the server.",
  48
+        22 =>
  49
+"HTTP  page  not  retrieved.  The  requested url was not found or returned another error with the HTTP error  code  being  400  or above. This return code only appears if -f/--fail is used.",
  50
+        23 => "Write  error.  Curl couldn't write data to a local filesystem or similar.",
  51
+        25 => "FTP couldn't STOR file. The server denied  the  STOR  operation, used for FTP uploading.",
  52
+        26 => "Read error. Various reading problems.",
  53
+        27 => "Out of memory. A memory allocation request failed.",
  54
+        28 => "Operation  timeout.  The  specified  time-out period was reached according to the conditions.",
  55
+        30 => "FTP PORT failed. The PORT command failed. Not  all  FTP  servers support  the  PORT  command,  try  doing  a  transfer using PASV instead!",
  56
+        31 => "FTP couldn't use REST. The REST command failed. This command  is used for resumed FTP transfers.",
  57
+        33 => "HTTP range error. The range 'command' didn't work.",
  58
+        34 => "HTTP post error. Internal post-request generation error.",
  59
+        35 => "SSL connect error. The SSL handshaking failed.",
  60
+        36 => "FTP  bad  download  resume. Couldn't continue an earlier aborted download.",
  61
+        37 => "FILE couldn't read file. Failed to open the file. Permissions?",
  62
+        38 => "LDAP cannot bind. LDAP bind operation failed.",
  63
+        39 => "LDAP search failed.",
  64
+        41 => "Function not found. A required LDAP function was not found.",
  65
+        42 => "Aborted by callback. An application told curl to abort the oper‐ation.",
  66
+        43 => "Internal error. A function was called with a bad parameter.",
  67
+        45 => "Interface  error.  A  specified  outgoing interface could not be used.",
  68
+        47 => "Too many redirects. When following redirects, curl hit the maxi‐mum amount.",
  69
+        48 => "Unknown TELNET option specified.",
  70
+        49 => "Malformed telnet option.",
  71
+        51 => "The peer's SSL certificate or SSH MD5 fingerprint was not ok.",
  72
+        52 => "The  server  didn't  reply anything, which here is considered an error.",
  73
+        53 => "SSL crypto engine not found.",
  74
+        54 => "Cannot set SSL crypto engine as default.",
  75
+        55 => "Failed sending network data.",
  76
+        56 => "Failure in receiving network data.",
  77
+        58 => "Problem with the local certificate.",
  78
+        59 => "Couldn't use specified SSL cipher.",
  79
+        60 => "Peer certificate cannot be authenticated with known CA  certifi‐cates.",
  80
+        61 => "Unrecognized transfer encoding.",
  81
+        62 => "Invalid LDAP URL.",
  82
+        63 => "Maximum file size exceeded.",
  83
+        64 => "Requested FTP SSL level failed.",
  84
+        65 => "Sending the data requires a rewind that failed.",
  85
+        66 => "Failed to initialise SSL Engine.",
  86
+        67 => "The  user  name, password, or similar was not accepted and curl failed to log in.",
  87
+        68 => "File not found on TFTP server.",
  88
+        69 => "Permission problem on TFTP server.",
  89
+        70 => "Out of disk space on TFTP server.",
  90
+        71 => "Illegal TFTP operation.",
  91
+        72 => "Unknown TFTP transfer ID.",
  92
+        73 => "File already exists (TFTP).",
  93
+        74 => "No such user (TFTP).",
  94
+        75 => "Character conversion failed.",
  95
+        76 => "Character conversion functions required.",
  96
+        77 => "Problem with reading the SSL CA cert (path? access rights?).",
  97
+        78 => "The resource referenced in the URL does not exist.",
  98
+        79 => "An unspecified error occurred during the SSH session.",
  99
+        80 => "Failed to shut down the SSL connection.",
  100
+        82 => "Could not load CRL file,  missing  or  wrong  format.",
  101
+        83 => "Issuer check failed",
  102
+    );
  103
+
  104
+    return \%codes;
  105
+}
  106
+
  107
+sub uid {
  108
+    my $self = shift;
  109
+
  110
+    return $self->parent()->ftp_uid();
  111
+}
  112
+
  113
+sub gid {
  114
+    my $self = shift;
  115
+
  116
+    return $self->parent()->ftp_gid();
  117
+}
  118
+
  119
+sub type {
  120
+    return 'cnc';
  121
+}
  122
+
  123
+sub _prepare {
  124
+    my $self = shift;
  125
+    my $host = shift;
  126
+    my $dbms = shift;
  127
+
  128
+    # turn of ssh host key checking
  129
+    $self->sys()->ssh_hostkey_check(0);
  130
+
  131
+    my $server_address = $self->config()->get_scalar('MTK::MYB::CNC::ServerAddress');
  132
+
  133
+    if ( !$server_address ) {
  134
+        $self->logger()->log(
  135
+            message =>
  136
+              'No server address given. You need to set THIS hosts external (wrt. your backup nodes) address to the key MTK::MYB::CNC::ServerAddress',
  137
+            level => 'error',
  138
+        );
  139
+        return;
  140
+    }
  141
+
  142
+    if ( !$host ) {
  143
+        $self->logger()->log( message => "No host given! Impossible to continue.", level => 'error', );
  144
+        return;
  145
+    }
  146
+
  147
+    if ( !$dbms ) {
  148
+        $self->logger()->log( message => "No dbms given! Impossible to continue.", level => 'error', );
  149
+        return;
  150
+    }
  151
+
  152
+    # check if pw-less ssh access works
  153
+    if ( !$self->sys()->run_remote_cmd( $host, '/bin/true' ) ) {
  154
+
  155
+        # report an error is pw-less ssh access does not work
  156
+        my $msg = 'Password-less SSH access to ' . $host . ' does not work. Public Keys setup ok? Aborting!';
  157
+        $self->logger()->log( message => $msg, level => 'error', );
  158
+        $self->status()->global( MTK::MYB::Codes::get_status_code('SSH-ERROR') );
  159
+        $self->_report();
  160
+        return;
  161
+    }
  162
+    else {
  163
+        $self->logger()->log( message => 'Password-less SSH access to '.$host.' is OK', level => 'debug', );
  164
+    }
  165
+
  166
+    # check ftp connection via ssh
  167
+    # lftp -u $dbms,$config->{$dbms}{'ftp-password'} $config->{'default'}{'server-address'} -e "quit"
  168
+    my $rv = $self->sys()->run_remote_cmd( $host, $self->_get_ftp_cmd_test($dbms), { ReturnRV => 1, } );
  169
+    if ( defined($rv) && $rv == 0 ) {
  170
+        $self->logger()->log( message => 'FTP access from ' . $host . ' is OK', level => 'debug', );
  171
+    }
  172
+    else {
  173
+
  174
+        # report an error if ftp-access from client does not work
  175
+        my $addn_error_msg = '';
  176
+        if ( $self->parent()->ftp_helper() eq 'curl' ) {
  177
+            my $error_msg = $self->curl_exitcodes()->{$rv} || 'n/a';
  178
+            $addn_error_msg = 'Curl exited with Code ' . $rv . ' which means: "' . $error_msg . '". ';
  179
+        }
  180
+        my $msg = 'FTP access from ' . $host . ' to ' . $server_address . ' does not work. ' . $addn_error_msg . ' Is lftp/curl installed? Aborting!';
  181
+        $self->logger()->log( message => $msg, level => 'error', );
  182
+        $self->status()->global( MTK::MYB::Codes::get_status_code('FTP-ERROR') );
  183
+        $self->_report();
  184
+        return;
  185
+    }
  186
+
  187
+    return 1;
  188
+}
  189
+
  190
+sub _binlog_archive {
  191
+    my $self = shift;
  192
+
  193
+    my $host = $self->hostname();
  194
+    $self->logger()->log( message => 'binlog_archive processing '.$host, level => 'debug', );
  195
+
  196
+    # Create final archive destination
  197
+    my $local_archive_dir = $self->dir_binlogs();
  198
+
  199
+    # Create temporary transfer destination
  200
+    my $local_copy_dir = $self->fs()->makedir( $self->fs()->filename( $self->dir_progress(), 'binlogs' ), { Uid => $self->uid(), Gid => $self->gid(), } );
  201
+
  202
+    # The binlogs must first be copied to the inprogress dir - since only this
  203
+    # directory is being exported by the ftpd - and the copied
  204
+    # to their final destination.
  205
+
  206
+    # get remote log-bin setting
  207
+    my $cmd = 'egrep "log(_|-)bin" /etc/mysql/my.cnf | cut -d"=" -f2';
  208
+    my $remote_log_bin = $self->sys()->run_remote_cmd( $host, $cmd, { CaptureOutput => 1, Chomp => 1, } );
  209
+    my $remote_source_dir = File::Basename::dirname($remote_log_bin);
  210
+
  211
+    # default = 10 years
  212
+    my $holdbacktime_binlog = $self->config()->get('MTK::MYB::Rotations::Binlogs') || 3650;
  213
+
  214
+    if ($remote_source_dir) {
  215
+        $self->logger()->log( message => 'Using Remote Binlog Source Directory :' . $remote_source_dir, level => 'debug', );
  216
+    }
  217
+    else {
  218
+        $self->logger()->log( message => 'Remote Binlog Source Directory not set! Returning.', level => 'error', );
  219
+        return;
  220
+    }
  221
+
  222
+    # make sure the temporary copy directory is defined and exists
  223
+    if ( $local_copy_dir && -d $local_copy_dir && $local_copy_dir ne '/' ) {
  224
+        $self->logger()->log( message => 'Using local copy directory: ' . $local_copy_dir, level => 'debug', );
  225
+    }
  226
+    else {
  227
+        $local_copy_dir ||= '';    # prevents an undefinedness warning
  228
+        $self->logger()->log( message => 'Local copy directory not defined or not accessible: ' . $local_copy_dir, level => 'error', );
  229
+        return;
  230
+    }
  231
+
  232
+    # make sure the final archive destination exists
  233
+    if ( $local_archive_dir && -d $local_archive_dir ) {
  234
+        $self->logger()->log( message => 'Using local archive directory: ' . $local_archive_dir, level => 'debug', );
  235
+    }
  236
+    else {
  237
+        $local_archive_dir ||= '';    # prevents an undefinedness warning
  238
+        $self->logger()->log( message => 'Local archive driectory not defined or not accessible: ' . $local_archive_dir, level => 'error', );
  239
+        return;
  240
+    }
  241
+
  242
+    my ( $sec, $min, $hour, $dayofmonth, $month, $year, $dayofweek, $dayofyear, $summertime ) = localtime(time);
  243
+    $year += 1900;
  244
+    $month++;
  245
+
  246
+    # Remove old (expired) archived binlogs [local]
  247
+    foreach my $file ( glob( $local_archive_dir . '/mysql-bin.*' ) ) {
  248
+        $self->logger()->log( message => 'Binlogarchive-Expire - File '.$file.' is ' . sprintf( '%.2f', -M $file ) . ' days old.', level => 'debug', );
  249
+        if ( -M $file > $holdbacktime_binlog ) {
  250
+            $self->logger()->log(
  251
+                message => 'Binlogarchive-Expire - File ' . $file . ' is too ' . sprintf( '%.2f', -M $file ) . ' old. Removing.',
  252
+                level   => 'debug',
  253
+            );
  254
+            $cmd = 'rm -f '.$file;
  255
+            $self->logger()->log( message => $cmd, level => 'debug', );
  256
+            $self->sys()->run_cmd($cmd) unless $self->dry();
  257
+        }
  258
+    }
  259
+
  260
+    # Archive new binlogs [remote]
  261
+    if ( $remote_source_dir && $remote_source_dir ne q{/} ) {
  262
+        $self->logger()->log( message => 'Continuing w/ remote binlog search path '.$remote_source_dir, level => 'debug', );
  263
+    }
  264
+    else {
  265
+        $self->logger()->log( message => 'Invalid remote binlog search path: '.$remote_source_dir, level => 'error', );
  266
+        return;
  267
+    }
  268
+
  269
+    $cmd = 'find ' . $remote_source_dir . ' -type f -name "mysql-bin.*"';
  270
+    my $out = $self->sys()->run_remote_cmd( $host, $cmd, { CaptureOutput => 1, Timeout => 1200, Chomp => 1, } );
  271
+
  272
+    $out ||= '';    # prevent definedness warnings
  273
+    foreach my $binlog_file ( split /\n/, $out ) {
  274
+        $self->logger()->log( message => 'Examining Binlog File: ' . $binlog_file, level => 'debug', );
  275
+
  276
+        my @srcpath = split /\//, $binlog_file;
  277
+        my $dst = $srcpath[-1] . $self->packer()->ext();
  278
+        if ( !-e $dst || -M $dst < 1 ) {
  279
+            if ( !-e $dst ) {
  280
+                $self->logger()->log( message => $binlog_file.' not present, creating '.$dst, level => 'debug', );
  281
+            }
  282
+            else {
  283
+                $self->logger()->log( message => $binlog_file.' not up-to-date, overwriting '.$dst, level => 'debug', );
  284
+            }
  285
+            $cmd = $self->_get_cmd_prefix();
  286
+            $cmd .= $self->packer()->cmd();
  287
+            $cmd .= q{ } . $binlog_file;
  288
+            $cmd .= q{ | };
  289
+            $cmd .= $self->_get_cmd_prefix() . $self->_get_ftp_cmd_stdin( 'binlogs', $dst );
  290
+            $self->logger()->log( message => 'CMD: ' . $cmd, level => 'debug', );
  291
+            $self->sys()->run_remote_cmd( $host, $cmd, {} );
  292
+        }
  293
+        else {
  294
+            $self->logger()->log( message => 'Binlogfile at ' . $dst . ' present and uptodate, skipping.', level => 'debug', );
  295
+        }
  296
+    }
  297
+
  298
+    # move archived binlogs from the incoming copy directory to their permanent storage location
  299
+    $cmd = q{mv -f }.$local_copy_dir.q{/* }.$local_archive_dir.q{/};
  300
+    if ( $self->sys()->run_cmd( $cmd, { Timeout => 3600, } ) ) {
  301
+        $self->logger()
  302
+          ->log( message => 'Moved binlogs from transfer directory at '.$local_copy_dir.' to final archive destination at '.$local_archive_dir, level => 'debug', );
  303
+        $cmd = q{rm -rf }.$local_copy_dir.q{/};
  304
+        if ( $self->sys()->run_cmd( $cmd, { Timeout => 3600, } ) ) {
  305
+            $self->logger()->log( message => 'Removed temporary transfer directory ' . $local_copy_dir, level => 'debug', );
  306
+        }
  307
+        else {
  308
+            $self->logger()->log( message => 'Failed to remove temporary transfer directory ' . $local_copy_dir, level => 'error', );
  309
+        }
  310
+    }
  311
+    else {
  312
+        $self->logger()->log(
  313
+            message => 'Failed to move binlogs from transfer directory at '.$local_copy_dir.' to final archive destination at '.$local_archive_dir,
  314
+            level   => 'error',
  315
+        );
  316
+    }
  317
+
  318
+    # remove old binlogs [remote]
  319
+    $cmd = $self->_get_cmd_prefix();
  320
+    $cmd .= '/usr/bin/find ' . $remote_source_dir . ' -type f -regex ".*mysql-bin.[0-9].*" -mtime +10 -print0 | /usr/bin/xargs -0 rm -f';
  321
+    $self->logger()->log( message => $cmd, level => 'debug', );
  322
+    if ( $self->sys()->run_remote_cmd( $host, $cmd, ) ) {
  323
+        $self->logger()->log( message => 'Removed old binlogs on remote host '.$host, level => 'debug', );
  324
+        return 1;
  325
+    }
  326
+    else {
  327
+        $self->logger()->log( message => 'Failed to remove old binlogs on remote host '.$host, level => 'debug', );
  328
+        return;
  329
+    }
  330
+}
  331
+
  332
+sub _get_cmd_suffix {
  333
+    my $self    = shift;
  334
+    my $db      = shift;
  335
+    my $table   = shift;
  336
+    my $type    = shift;
  337
+    my $destdir = shift;
  338
+    my $file    = shift;
  339
+
  340
+    my $cmd = ' | ' . $self->_get_cmd_prefix() . $self->_get_ftp_cmd_base('--ftp-create-dirs -T -');
  341
+    if ( $self->parent()->ftp_helper() eq 'curl' ) {
  342
+        $cmd .= q{/} . $type . q{/} . $db . q{/} . $file . $self->packer()->ext();
  343
+    }
  344
+    else {
  345
+        $cmd .= ' -e "cd ' . $type . '; cd ' . $db . '; put /dev/stdin -o ' . $file . $self->packer()->ext() . '; quit"';
  346
+    }
  347
+
  348
+    return $cmd;
  349
+}
  350
+
  351
+sub _get_ftp_cmd_base {
  352
+    my $self = shift;
  353
+    my $opts = shift || '';
  354
+
  355
+    my $dbms = $self->dbms();
  356
+
  357
+    my $password       = $self->config()->get( 'MTK::MYB::DBMS::' . $dbms . '::Password' );
  358
+    my $server_address = $self->config()->get('MTK::MYB::CNC::ServerAddress');
  359
+    my $ftp_cmd_base;
  360
+
  361
+    if ( $self->parent()->ftp_helper() eq 'curl' ) {
  362
+        $ftp_cmd_base = '/usr/bin/curl ' . $opts . ' -s -4 -u ' . $dbms . q{:} . $password . ' ftp://' . $server_address . q{:} . $self->parent()->ftp_port();
  363
+    }
  364
+    else {
  365
+        $ftp_cmd_base = '/usr/bin/lftp ' . $opts . q{ -p } . $self->parent()->ftp_port() . q{ -u } . $dbms . q{,"} . $password . q{" } . $server_address;
  366
+    }
  367
+
  368
+    return $ftp_cmd_base;
  369
+}
  370
+
  371
+sub _get_ftp_cmd_stdin {
  372
+    my $self      = shift;
  373
+    my $dest_dir  = shift;
  374
+    my $dest_file = shift;
  375
+
  376
+    if ( $self->parent()->ftp_helper() eq 'curl' ) {
  377
+        return $self->_get_ftp_cmd_base('--ftp-create-dirs -T -') . q{/} . $dest_dir . q{/} . $dest_file;
  378
+    }
  379
+    else {
  380
+        return $self->_get_ftp_cmd_base() . ' -e "cd ' . $dest_dir . '; put /dev/stdin -o ' . $dest_file . '; quit"';
  381
+    }
  382
+}
  383
+
  384
+sub _get_ftp_cmd_test {
  385
+    my $self = shift;
  386
+
  387
+    if ( $self->parent()->ftp_helper() eq 'curl' ) {
  388
+        return $self->_get_ftp_cmd_base() . q{/};
  389
+    }
  390
+    else {
  391
+        return $self->_get_ftp_cmd_base() . '-e "quit"';
  392
+    }
  393
+}
  394
+
  395
+no Moose;
  396
+__PACKAGE__->meta->make_immutable;
  397
+
  398
+1;
  399
+
  400
+__END__
  401
+
  402
+=head1 NAME
  403
+
  404
+MTK::MYB::CNC::Worker - The Workhorse for the centralized (CNC) MySQLBackup
  405
+
  406
+=head1 SYNOPSIS
  407
+
  408
+    use MTK::MYB::CNC::Worker;
  409
+    my $Worker = MTK::MYB::CNC::Worker::->new(
  410
+        {
  411
+            'config'  => $self->config(),
  412
+            'logger'  => $self->logger(),
  413
+            'parent'  => $self->parent(),
  414
+            'verbose' => $self->verbose(),
  415
+            'dry'     => $self->dry(),
  416
+            'bank'    => $self->bank(),
  417
+            'vault'   => $self->vault(),
  418
+        }
  419
+    );
  420
+    $Worker->run();
  421
+    
  422
+=head1 DESCRIPTION
  423
+
  424
+This class implements the business logic of the CNC MySQL Backup solution. It
  425
+extends MTK::MYB::Worker and overrides some of its methods.
  426
+
  427
+=method run
  428
+
  429
+Once the class has been set up call this method to start the backup process.
  430
+
  431
+=method type
  432
+
  433
+This method is primarily usefull for subclassing this class. It is used to
  434
+dynamically determine the exact subtype of itself. Of course this
  435
+could as well be done by using ISA and/or ref. Howevery this way is more
  436
+straight forward and easier to implement.
  437
+
  438
+=method uid
  439
+
  440
+The ftp uid.
  441
+
  442
+=method gid
  443
+
  444
+The ftp gid.
  445
+
  446
+=head1 ACKNOWLEDGEMENT
  447
+
  448
+This module was originally developed for eGENTIC Systems. With approval from eGENTIC Systems,
  449
+this module was generalized and published, for which the authors would like to express their
  450
+gratitude.
  451
+
  452
+=cut
89  lib/MTK/MYB/Cmd/Command/cnc.pm
... ...
@@ -0,0 +1,89 @@
  1
+package MTK::MYB::Cmd::Command::cnc;
  2
+# ABSTRACT: CNC Mysqlbackup command
  3
+
  4
+use 5.010_000;
  5
+use mro 'c3';
  6
+use feature ':5.10';
  7
+
  8
+use Moose;
  9
+use namespace::autoclean;
  10
+
  11
+# use IO::Handle;
  12
+# use autodie;
  13
+# use MooseX::Params::Validate;
  14
+# use Carp;
  15
+# use English qw( -no_match_vars );
  16
+# use Try::Tiny;
  17
+use Linux::Pidfile;
  18
+use MTK::MYB::CNC;
  19
+
  20
+# extends ...
  21
+extends 'MTK::MYB::Cmd::Command';
  22
+# has ...
  23
+has '_pidfile' => (
  24
+    'is'    => 'ro',
  25
+    'isa'   => 'Linux::Pidfile',
  26
+    'lazy'  => 1,
  27
+    'builder' => '_init_pidfile',
  28
+);
  29
+# with ...
  30
+# initializers ...
  31
+sub _init_pidfile {
  32
+    my $self = shift;
  33
+
  34
+    my $PID = Linux::Pidfile::->new({
  35
+        'pidfile'   => $self->config()->get('MTK::MYB::Pidfile', { Default => '/var/run/myb.pid', }),
  36
+        'logger'    => $self->logger(),
  37
+    });
  38
+
  39
+    return $PID;
  40
+}
  41
+
  42
+# your code here ...
  43
+sub execute {
  44
+    my $self = shift;
  45
+
  46
+    $self->_pidfile()->create() or die('Script already running.');
  47
+
  48
+    my $MYB = MTK::MYB::CNC::->new({
  49
+        'config'    => $self->config(),
  50
+        'logger'    => $self->logger(),
  51
+    });
  52
+
  53
+    my $status = $MYB->run();
  54
+
  55
+    $self->_pidfile()->remove();
  56
+
  57
+    return $status;
  58
+}
  59
+
  60
+sub abstract {
  61
+    return 'Run the CNC variant of Mysqlbackup to perform remote backups';
  62
+}
  63
+
  64
+no Moose;