~diego-fmpwizard/mysql-sandbox/repl-topo-aggr-support

« back to all changes in this revision

Viewing changes to bin/sbtool

  • Committer: Giuseppe Maxia
  • Date: 2009-03-29 09:38:55 UTC
  • Revision ID: g.maxia@gmail.com-20090329093855-uie8syy1r12x6brj
- Preparation for version 3.0
- refactoring code to use with ExtUtilis::MakeMaker and install a proper Perl module 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/perl
 
2
#    The MySQL Sandbox
 
3
#    Copyright (C) 2009 Giuseppe Maxia
 
4
#    Contacts: http://datacharmer.org
 
5
#
 
6
#    This program is free software; you can redistribute it and/or modify
 
7
#    it under the terms of the GNU General Public License as published by
 
8
#    the Free Software Foundation; version 2 of the License
 
9
#
 
10
#    This program is distributed in the hope that it will be useful,
 
11
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
#    GNU General Public License for more details.
 
14
#
 
15
#    You should have received a copy of the GNU General Public License
 
16
#    along with this program; if not, write to the Free Software
 
17
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
18
 
 
19
 
 
20
use strict;
 
21
use warnings;
 
22
use Carp;
 
23
 
 
24
use English qw( -no_match_vars );
 
25
use Data::Dumper;
 
26
use Getopt::Long qw(:config no_ignore_case );
 
27
use File::Copy qw/cp/;
 
28
use File::Find;
 
29
use MySQL::Sandbox;
 
30
 
 
31
my $DEBUG = $MySQL::Sandbox::DEBUG;
 
32
 
 
33
my $sandbox_options_file    = "my.sandbox.cnf";
 
34
my $sandbox_current_options = "current_options.conf";
 
35
 
 
36
my %supported_operations = (
 
37
    ports => 'lists ports used by the Sandbox',
 
38
    range => 'finds N consecutive ports not yet used by the Sandbox',
 
39
    info  => 'returns configuration options from a Sandbox',
 
40
    tree  => 'creates a replication tree',
 
41
    copy  => 'copies data from one Sandbox to another',
 
42
    move  => 'moves a Sandbox to a different location',
 
43
    port  => 'Changes a Sandbox port',
 
44
);
 
45
 
 
46
my %supported_formats = (
 
47
    text => 'plain text dump of requested information',
 
48
    perl => 'fully structured information in Perl code',
 
49
);
 
50
 
 
51
my %parse_options = (
 
52
    operation => {
 
53
        so       => 10,
 
54
        parse    => 'o|operation=s',
 
55
        value    => undef,
 
56
        accepted => \%supported_operations,
 
57
        help     => 'what task to perform',
 
58
    },
 
59
    source_dir => {
 
60
        so    => 20,
 
61
        parse => 's|source_dir=s',
 
62
        value => undef,
 
63
        help  => 'source directory for move,copy',
 
64
    },
 
65
    dest_dir => {
 
66
        so    => 30,
 
67
        parse => 'd|dest_dir=s',
 
68
        value => undef,
 
69
        help  => 'destination directory for move,copy',
 
70
    },
 
71
    new_port  => {
 
72
        so    => 40,
 
73
        parse => 'n|new_port=s',
 
74
        value => undef,
 
75
        help  => 'new port while moving a sandbox',
 
76
    },
 
77
    only_used => {
 
78
        so    => 50,
 
79
        parse => 'u|only_used',
 
80
        value => 0,
 
81
        help  => 'for "ports" operation, shows only the used ones',
 
82
    },
 
83
    min_range => {
 
84
        so    => 60,
 
85
        parse => 'i|min_range=i',
 
86
        value => 5000,
 
87
        help  => 'minimum port when searching for available ranges',
 
88
    },
 
89
    max_range => {
 
90
        so    => 70,
 
91
        parse => 'x|max_range=i',
 
92
        value => 32000,
 
93
        help  => 'maximum port when searching for available ranges',
 
94
    },
 
95
    range_size => {
 
96
        so    => 80,
 
97
        parse => 'z|range_size=i',
 
98
        value => 10,
 
99
        help  => 'size of range when searching for available port range',
 
100
    },
 
101
    format => {
 
102
        so       => 90,
 
103
        parse    => 'f|format=s',
 
104
        value    => 'text',
 
105
        accepted => \%supported_formats,
 
106
        help     => 'format for "ports" and "info"',
 
107
    },
 
108
    search_path => {
 
109
        so    => 100,
 
110
        parse => 'p|search_path=s',
 
111
        value => $ENV{SANDBOX_HOME},
 
112
        help  => 'search path for ports and info',
 
113
    },
 
114
    all_info => {
 
115
        so    => 110,
 
116
        parse => 'a|all_info',
 
117
        value => 0,
 
118
        help  => 'print more info for "ports" operation'
 
119
    },
 
120
    tree_nodes => {
 
121
        so    => 120,
 
122
        parse => 'tree_nodes=s',
 
123
        value => '',
 
124
        help  => 'description of the tree (x-x x x-x x|x x x|x x)',
 
125
    },
 
126
    mid_nodes => {
 
127
        so    => 130,
 
128
        parse => 'mid_nodes=s',
 
129
        value => '',
 
130
        help  => 'description of the middle nodes (x x x)',
 
131
    },
 
132
    leaf_nodes => {
 
133
        so    => 140,
 
134
        parse => 'leaf_nodes=s',
 
135
        value => '',
 
136
        help  => 'description of the leaf nodes (x x|x x x|x x)',
 
137
    },
 
138
    tree_dir => {
 
139
        so    => 150,
 
140
        parse => 'tree_dir=s',
 
141
        value => '',
 
142
        help  => 'which directory contains the tree nodes',
 
143
    },
 
144
    verbose => {
 
145
        so    => 160,
 
146
        parse => 'v|verbose',
 
147
        value => 0,
 
148
        help  => 'prints more info on some operations'
 
149
    },
 
150
    help => {
 
151
        so    => 999,
 
152
        parse => 'h|help',
 
153
        value => undef,
 
154
        help  => 'this screen',
 
155
    },
 
156
);
 
157
 
 
158
my %options = map { $_, $parse_options{$_}{value} } keys %parse_options;
 
159
 
 
160
GetOptions( map { $parse_options{$_}{parse}, \$options{$_} }
 
161
      keys %parse_options )
 
162
  or get_help();
 
163
 
 
164
get_help() if $options{help} or ! $options{operation};
 
165
 
 
166
if ($options{verbose}) {
 
167
    $DEBUG = $options{verbose} unless $DEBUG;
 
168
}
 
169
 
 
170
for my $op ( keys %parse_options ) {
 
171
    if ( $parse_options{$op}{accepted} ) {
 
172
        my %accepted = %{ $parse_options{$op}{accepted} };
 
173
        for my $ak ( keys %accepted ) {
 
174
            unless ( exists $accepted{ $options{$op} } ) {
 
175
                croak "invalid value '$options{$op}' for option <$op>\n";
 
176
            }
 
177
        }
 
178
    }
 
179
}
 
180
 
 
181
for my $dir (qw(source_dir dest_dir tree_dir search_path)) {
 
182
    if ($options{$dir}) {
 
183
        $options{$dir} =~ s/^\s*~/$ENV{HOME}/;
 
184
    }
 
185
}
 
186
 
 
187
if ( $options{operation} eq 'ports' ) {
 
188
    get_ports();
 
189
}
 
190
elsif ( $options{operation} eq 'info' ) {
 
191
    $options{all_info} = 1;
 
192
    $options{format} = 'perl';
 
193
    get_ports();
 
194
}
 
195
elsif ( $options{operation} eq 'range' ) {
 
196
    get_ranges()
 
197
}
 
198
elsif ( $options{operation} eq 'tree' ) {
 
199
    make_tree($options{tree_dir} )
 
200
}
 
201
elsif ( $options{operation} eq 'move' ) {
 
202
    move_sandbox( $options{source_dir}, $options{dest_dir} );
 
203
}
 
204
elsif ( $options{operation} eq 'port' ) {
 
205
    unless ($options{new_port}) {
 
206
        croak "operation 'port' requires new_port option\n";
 
207
    }
 
208
    move_sandbox( $options{source_dir}, $options{source_dir}, 'alreday_moved' );
 
209
}
 
210
elsif ( $options{operation} eq 'copy' ) {
 
211
    copy_single_sandbox( $options{source_dir}, $options{dest_dir} );
 
212
}
 
213
else {
 
214
    croak "unsupported operation ($options{operation})\n";
 
215
}
 
216
 
 
217
sub get_ranges {
 
218
    my ( $ports, $all_info ) = get_sb_info();
 
219
    my $minimum_port = $options{min_range};
 
220
    my $maximum_port = $options{max_range};
 
221
    my $range_size   = $options{range_size};
 
222
    if ( $minimum_port >= $maximum_port ) {
 
223
        croak "minimum range must be lower than the maximum range\n";
 
224
    }
 
225
    if ( ( $minimum_port + $range_size ) > $maximum_port ) {
 
226
        croak "range too wide for given boundaries\n";
 
227
    }
 
228
    my $range_found = 0;
 
229
  range_search:
 
230
    while ( !$range_found ) {
 
231
        if ( $minimum_port >= $maximum_port ) {
 
232
            croak "can't find a range of $range_size "
 
233
              . "free ports between "
 
234
              . "$options{min_range} and $options{max_range}\n";
 
235
        }
 
236
        for my $i ( $minimum_port .. $minimum_port + $range_size ) {
 
237
            if ( exists $ports->{$i} or ( $i >= $maximum_port ) ) {
 
238
                $minimum_port = $i + 1;
 
239
                next range_search;
 
240
            }
 
241
        }
 
242
        $range_found = 1;
 
243
    }
 
244
    printf "%5d - %5d\n", $minimum_port , $minimum_port + $range_size;
 
245
}
 
246
 
 
247
sub get_ports {
 
248
    my ( $ports, $all_info ) = get_sb_info();
 
249
 
 
250
    if ( $options{format} eq 'perl' ) {
 
251
        print Data::Dumper->Dump( [$ports], ['ports'] );
 
252
        print Data::Dumper->Dump( [$all_info], ['all_info'] )
 
253
          if $options{all_info};
 
254
    }
 
255
    elsif ( $options{format} eq 'text' ) {
 
256
        for my $port ( sort { $a <=> $b } keys %$ports ) {
 
257
            printf "%5d %2d\n", $port, $ports->{$port};
 
258
        }
 
259
    }
 
260
    else {
 
261
        croak "unrecognized format -> $options{format}\n";
 
262
    }
 
263
    return ( $ports, $all_info );
 
264
}
 
265
 
 
266
sub get_sb_info {
 
267
    my ($search_path) = @_;
 
268
    my %ports         = ();
 
269
    my %all_info      = ();
 
270
    my $seen_dir      = '';
 
271
 
 
272
    find(
 
273
        {
 
274
            no_chdir => 1,
 
275
            wanted   => sub {
 
276
                if ( $seen_dir eq $File::Find::dir ) {
 
277
                    return;
 
278
                }
 
279
                my $params;
 
280
                if ( $params = get_sandbox_params($File::Find::dir, 1) ) {
 
281
                    $seen_dir = $File::Find::dir;
 
282
                    my $port = $params->{opt}{port};
 
283
                    if (   -f $params->{opt}{pid_file}
 
284
                        && -e $params->{opt}{socket} )
 
285
                    {
 
286
                        $ports{$port} = 1;
 
287
                        $all_info{$port} = $params if $options{all_info};
 
288
                    }
 
289
                    else {
 
290
                        unless ( $options{only_used} ) {
 
291
                            $ports{$port} = 0;
 
292
                            $all_info{$port} = $params if $options{all_info};
 
293
                        }
 
294
                    }
 
295
                }
 
296
              }
 
297
        },
 
298
        $search_path || $options{search_path}
 
299
    );
 
300
    return ( \%ports, \%all_info );
 
301
}
 
302
 
 
303
sub get_sandbox_params {
 
304
    my ($dir, $skip_strict) = @_;
 
305
    confess "directory name required\n" unless $dir;
 
306
    confess "directory $dir doesn't exist\n" unless -d $dir;
 
307
    unless (is_a_sandbox($dir)) {
 
308
        confess "directory <$dir> must be a sandbox\n" unless $skip_strict;
 
309
    }
 
310
    my %params = (
 
311
        opt  => undef,
 
312
        conf => undef
 
313
    );
 
314
    if ( -f "$dir/$sandbox_options_file" ) {
 
315
        $params{opt} = get_option_file_contents("$dir/$sandbox_options_file");
 
316
    }
 
317
    else {
 
318
        # warn "options file $dir not found\n";
 
319
        return;
 
320
    }
 
321
    if ( -f "$dir/$sandbox_current_options" ) {
 
322
        $params{conf} =
 
323
          get_option_file_contents("$dir/$sandbox_current_options");
 
324
    }
 
325
    else {
 
326
        # warn "current conf file not found\n";
 
327
        return;
 
328
    }
 
329
    return \%params;
 
330
}
 
331
 
 
332
sub get_option_file_contents {
 
333
    my ($file) = @_;
 
334
    confess "file name required\n" unless $file;
 
335
    confess "file $file doesn't exist\n" unless -f $file;
 
336
    my %options;
 
337
    open my $RFILE, q{<}, $file
 
338
      or confess "can't open file $file\n";
 
339
    while ( my $line = <$RFILE> ) {
 
340
        next if $line =~ /^\s*$/;
 
341
        next if $line =~ /^\s*#/;
 
342
        next if $line =~ /^\s*\[/;
 
343
        chomp $line;
 
344
        my ( $key, $val ) = split /\s*=\s*/, $line;
 
345
        $key =~ s/-/_/g;
 
346
        $options{$key} = $val;
 
347
    }
 
348
    close $RFILE;
 
349
    # print Dumper(\%options) ; exit;
 
350
    return \%options;
 
351
}
 
352
 
 
353
sub get_help {
 
354
    my ($msg) = @_;
 
355
    if ($msg) {
 
356
        print '*' x 50;
 
357
        print "\n", $msg, "\n";
 
358
        print '*' x 50;
 
359
        print "\n";
 
360
    }
 
361
    print "usage: $PROGRAM_NAME [options] \n";
 
362
    for my $op ( sort { $parse_options{$a}{so} <=> $parse_options{$b}{so} }
 
363
        keys %parse_options )
 
364
    {
 
365
        my $val      = $options{$op};
 
366
        my $parse    = $parse_options{$op}{parse};
 
367
        my $expected = '-';
 
368
        if ( $parse =~ s/=(.+)// ) {
 
369
            $expected = $1;
 
370
        }
 
371
        my ( $short, $long ) = split /\|/, $parse;
 
372
        unless ($long) {
 
373
            $long = $short ;
 
374
            $short = ''; 
 
375
        }
 
376
        printf "\t%s%-5s --%-15s (%s) <%s> - %s\n", 
 
377
                ($short? '-' : ' '), 
 
378
                $short, 
 
379
                $long, 
 
380
                $expected, 
 
381
                $val || '',
 
382
                $parse_options{$op}{help} || '';
 
383
        if ( $parse_options{$op}{accepted} ) {
 
384
            my %accepted = %{ $parse_options{$op}{accepted} };
 
385
            for my $ao ( keys %accepted ) {
 
386
                printf "\t\t %-10s %s\n", "'$ao'", $accepted{$ao};
 
387
            }
 
388
        }
 
389
    }
 
390
    exit 1;
 
391
}
 
392
 
 
393
sub make_tree {
 
394
    my ($dir) = @_;
 
395
    unless ( $dir) {
 
396
        croak "you must set the directory using the 'tree_dir' option\n";
 
397
    }
 
398
    unless ( -d $dir ) {
 
399
        croak "directory ($dir) does not exist\n";
 
400
    }
 
401
    my $master = 1;
 
402
    if ($options{tree_nodes} ) {
 
403
        my ($m, $mid, $leaf) = split /-/, $options{tree_nodes};
 
404
        if ($m) {
 
405
            $master = $m;
 
406
        }
 
407
        else {
 
408
            croak " master not defined in tree_nodes\n";
 
409
        } 
 
410
        if ($mid) {
 
411
            $options{mid_nodes} = $mid;
 
412
        }
 
413
        else {
 
414
            croak " middle nodes not defined in tree_nodes\n";
 
415
        } 
 
416
        if ($leaf) {
 
417
            $options{leaf_nodes} = $leaf;
 
418
        }
 
419
        else {
 
420
            croak " leaf nodes not defined in tree_nodes\n";
 
421
        } 
 
422
    
 
423
    }
 
424
    my @MID_NODES = split ' ', $options{mid_nodes}
 
425
        or croak "no mid nodes selected. Use the --mid_nodes option\n"; 
 
426
    for my $mid (@MID_NODES) {
 
427
        croak "middle nodes must be numeric" unless $mid =~ /^\d+$/;
 
428
    }
 
429
    my @LEAF_NODES = ();
 
430
    
 
431
    my @chunks = split /\|/, $options{leaf_nodes}
 
432
        or croak "no leaf nodes selected. Use the --leaf_nodes option\n"; 
 
433
    # print Data::Dumper->Dump([\@chunks], ['chunks']);
 
434
    for my $c (@chunks) {
 
435
        my @leaf = split ' ', $c;
 
436
        croak "empty leaf node\n" unless @leaf;
 
437
        for my $ln (@leaf) {
 
438
            croak "leaf nodes must be numeric" unless $ln =~ /^\d+$/;
 
439
        }
 
440
        push @LEAF_NODES, [@leaf];
 
441
    }
 
442
 
 
443
    # print Data::Dumper->Dump([\@MID_NODES], ['MID_NODES']);
 
444
    # print Data::Dumper->Dump([\@LEAF_NODES], ['LEAF_NODES']);
 
445
    if ( @LEAF_NODES != @MID_NODES) {
 
446
        croak "you must specify at least one leaf node for each middle node\n";
 
447
    }
 
448
 
 
449
    for my $node (( $master, @MID_NODES, map {@$_} @LEAF_NODES)) {
 
450
        if ( ! -d  "$dir/node$node" ) {
 
451
            croak "node $node does not exist\n";
 
452
        }
 
453
    }
 
454
 
 
455
    my ($N1INFO, $N1PORT)=get_node_info($dir, $master);
 
456
 
 
457
    unless ($N1PORT) {
 
458
         croak "can't get the port for node$master\n"
 
459
             . "make sure the node is running\n";
 
460
    }
 
461
 
 
462
    my $CHANGE_MASTER_Q= "CHANGE MASTER TO master_host='127.0.0.1', "
 
463
        . "master_user='msandbox', master_password='msandbox',"
 
464
        . "master_port=";
 
465
 
 
466
    print "$dir/stop_all\n";
 
467
    system "$dir/stop_all";
 
468
 
 
469
    print "node $master is master\n";
 
470
    unless ( -e  $N1INFO->{opt}{socket}) {
 
471
        system "$dir/node$master/start"
 
472
    }
 
473
 
 
474
    system qq(echo "$dir/use_all 'stop slave'" > $dir/clear_all);
 
475
    system qq(echo "$dir/use_all 'stop slave'" > $dir/stop_all);
 
476
    system qq(echo "" > $dir/send_kill_all);
 
477
    system qq(echo "$dir/node$master/start" > $dir/start_all);
 
478
    for my $mid_node ( @MID_NODES ) {
 
479
 
 
480
        my ($MID_NODE_INFO, $MID_NODE_PORT)=get_node_info($dir, $mid_node);
 
481
        unless ( -e  $MID_NODE_INFO->{opt}{socket}) {
 
482
            system "$dir/node$mid_node/start";
 
483
        }
 
484
        my $HAS_UPDATES=`grep log_slave_updates $dir/node$mid_node/my.sandbox.cnf`;
 
485
        my $HAS_REPORT=`grep "report-host" $dir/node$mid_node/my.sandbox.cnf`;
 
486
 
 
487
        unless ($HAS_REPORT) {
 
488
          system qq(  echo "report-host=node$mid_node" >> $dir/node$mid_node/my.sandbox.cnf) ;
 
489
          system qq(  echo "report-port=$N1PORT" >> $dir/node$mid_node/my.sandbox.cnf) ;
 
490
        } 
 
491
        unless ( $HAS_UPDATES) {
 
492
            print  "enabling node $mid_node to relay updates\n";
 
493
            system qq(echo "log_slave_updates" >> $dir/node$mid_node/my.sandbox.cnf) ;
 
494
            system qq($dir/node$mid_node/restart) ;
 
495
        } 
 
496
         
 
497
        system qq($dir/n$mid_node -e "stop slave") ;
 
498
        system qq($dir/n$mid_node -e "$CHANGE_MASTER_Q $N1PORT") ;
 
499
        system qq($dir/n$mid_node -e "start slave") ;
 
500
        print "    node $mid_node is slave of node $master\n";
 
501
        my $l_nodes = shift @LEAF_NODES;
 
502
        system qq(echo "$dir/node$mid_node/start" >> $dir/start_all);
 
503
        for my $leaf_node (@$l_nodes) {
 
504
            my ($LEAF_NODE_INFO, $LN_PORT) = get_node_info($dir, $leaf_node);
 
505
            unless ( -e  $LEAF_NODE_INFO->{opt}{socket}) {
 
506
                system "$dir/node$leaf_node/start";
 
507
            }
 
508
            check_report($dir,$leaf_node, 1, $MID_NODE_PORT); 
 
509
            system qq($dir/n$leaf_node -e "stop slave");
 
510
            system qq($dir/n$leaf_node -e "$CHANGE_MASTER_Q $MID_NODE_PORT");
 
511
            system qq($dir/n$leaf_node -e "start slave");
 
512
            print "        node $leaf_node is slave of node $mid_node\n";
 
513
            system qq(echo "$dir/node$leaf_node/stop" >> $dir/stop_all);
 
514
            system qq(echo "$dir/node$leaf_node/clear" >> $dir/clear_all);
 
515
            system qq(echo "$dir/node$leaf_node/send_kill" >> $dir/send_kill_all);
 
516
            system qq(echo "$dir/node$leaf_node/start" >> $dir/start_all);
 
517
        }
 
518
        system qq(echo "$dir/node$mid_node/stop" >> $dir/stop_all);
 
519
        system qq(echo "$dir/node$mid_node/clear" >> $dir/clear_all);
 
520
        system qq(echo "$dir/node$mid_node/send_kill" >> $dir/send_kill_all);
 
521
    }
 
522
    system qq(echo "$dir/node$master/stop" >> $dir/stop_all);
 
523
    system qq(echo "$dir/node$master/clear" >> $dir/clear_all);
 
524
    system qq(echo "$dir/node$master/send_kill" >> $dir/send_kill_all);
 
525
}
 
526
 
 
527
sub check_report {
 
528
    my ($dir, $node, $restart, $master_port) = @_;
 
529
    my $HAS_REPORT=`grep "report-host" $dir/node$node/my.sandbox.cnf`;
 
530
    unless ($HAS_REPORT) {
 
531
        system qq(echo "report-host=node$node" >> $dir/node$node/my.sandbox.cnf);
 
532
        system qq(echo "report-port=$master_port" >> $dir/node$node/my.sandbox.cnf);
 
533
        if ($restart) {
 
534
            system qq($dir/node$node/restart);
 
535
        }
 
536
    }
 
537
 }
 
538
 
 
539
sub get_node_info {
 
540
    my ($dir, $node) = @_;
 
541
    my ( $info ) = get_sandbox_params("$dir/node$node");
 
542
    # print Dumper($ports, $all_info);
 
543
    confess "can't read port for node $node" unless $info; 
 
544
    return ($info, $info->{opt}{port});
 
545
}
 
546
 
 
547
sub move_sandbox {
 
548
    my ($source, $dest) = @_;
 
549
    unless ($source) {
 
550
        croak "Need a source directory (--source_dir)\n";
 
551
    }
 
552
    unless ($dest) {
 
553
        croak "Need a destination directory (--dest_dir)\n";
 
554
    }
 
555
    $dest   =~ s/^\s//;
 
556
    $dest   =~ s/\s*$//;
 
557
    $source =~ s/^\s//;
 
558
    $source =~ s/\s*$//;
 
559
    $source =~ s/^\s*~/$ENV{HOME}/;
 
560
    $dest   =~ s/^\s*~/$ENV{HOME}/;
 
561
    unless (($source =~ m{^/}) && ($dest =~ m{^/}) ) {
 
562
        croak "Source and destination directories must be absolute paths.\n"; 
 
563
    }
 
564
    unless ( -d $source )  {
 
565
        croak "directory $source does not exist\n";
 
566
    }
 
567
    if ( -x "$source/start") {
 
568
        if (( $source eq $dest) and $options{new_port}) {
 
569
            move_single_sandbox($source, $dest, "already_moved");
 
570
        }
 
571
        else {
 
572
            move_single_sandbox($source, $dest);
 
573
        }
 
574
    }
 
575
    elsif ( -x "$source/start_all") {
 
576
        move_multiple_sandbox($source, $dest);
 
577
    }
 
578
    else {
 
579
        croak "directory $source does not seem to be a sandbox\n";
 
580
    }
 
581
}
 
582
 
 
583
sub move_multiple_sandbox {
 
584
    my ($old_dir, $new_dir) = @_;
 
585
    unless ( -d $old_dir ) {
 
586
        croak " directory $old_dir doesn't exist\n";
 
587
    }
 
588
    if ( -d $new_dir ) {
 
589
        croak "directory $new_dir already exists\n";
 
590
    }
 
591
    if ( -x "$old_dir/stop_all" ) {
 
592
        system "$old_dir/stop_all";
 
593
        my $timeout = 5;
 
594
        while ( file_exists($old_dir, '\.pid$')) {
 
595
            $timeout--;
 
596
            sleep 1;
 
597
        }
 
598
    }
 
599
    else {
 
600
        croak "$old_dir does not seem to contain a multiple sandbox\n";
 
601
    }
 
602
    my @old_subdirs = grep { -d $_ } glob("$old_dir/*/");
 
603
    for my $od (@old_subdirs) {
 
604
        unless ( -x "$od/change_paths" ) {
 
605
            croak "directory $od is not a sandbox created with version 2.0.15+\n";
 
606
        }
 
607
    }
 
608
    my $result = system "mv $old_dir $new_dir";
 
609
    if ($result) {
 
610
        croak "unable to move sandbox $old_dir to $new_dir ($OS_ERROR)\n";
 
611
    }
 
612
    my @new_subdirs = ();
 
613
    for my $od (@old_subdirs) {
 
614
        my $nd = $od;
 
615
        if (($nd =~ s/$old_dir/$new_dir/ ) && ( -d $nd )) {
 
616
            push @new_subdirs, [$od, $nd];
 
617
        }
 
618
        else {
 
619
            # reverting to old directory
 
620
            system "mv $new_dir $old_dir";
 
621
            croak "can't move directory $od to $nd\n";
 
622
        }
 
623
    }
 
624
    for my $sd (@new_subdirs) {
 
625
        move_single_sandbox( $sd->[0], $sd->[1], "already_moved" ); 
 
626
    }
 
627
    chdir $new_dir;
 
628
    unless ($old_dir =~ m{/$} ) {
 
629
        $old_dir .= '/';
 
630
    }
 
631
    unless ($new_dir =~ m{/$} ) {
 
632
        $new_dir .= '/';
 
633
    }
 
634
    my @nodes  = glob("n[0-9]*");
 
635
    my @slaves = glob("s[0-9]*");
 
636
    my @scripts = qw(m start_all stop_all clear_all send_kill_all 
 
637
                  check_slaves use_all initialize_slaves);
 
638
    for my $script (( @nodes, @slaves, @scripts ) ) {
 
639
        if ( -x $script ) {
 
640
            system q(perl -i.bak -pe 'BEGIN{$old=shift;$new=shift};s/$old/$new/g') 
 
641
                   . " $old_dir $new_dir $script " ;
 
642
        }
 
643
    }
 
644
}
 
645
 
 
646
sub file_exists {
 
647
    my ($dir,$pattern) = @_;
 
648
    my $file_count =0;
 
649
    find (
 
650
        sub {
 
651
            $file_count++ if $File::Find::name =~ /$pattern/;
 
652
        },
 
653
        $dir
 
654
    );
 
655
    return $file_count;
 
656
}
 
657
 
 
658
sub move_single_sandbox {
 
659
    my ($old_dir, $new_dir, $already_moved) = @_;
 
660
    unless ( $already_moved) {
 
661
        unless ( -d $old_dir ) {
 
662
            croak " directory $old_dir doesn't exist\n";
 
663
        }
 
664
    }
 
665
    if ( -d $new_dir && (! $already_moved ) ) {
 
666
        croak "directory $new_dir already exists\n";
 
667
    }
 
668
    unless ( (-e "$old_dir/change_paths") 
 
669
             or ( $already_moved && -e "$new_dir/change_paths")  ) {
 
670
        croak   "script 'change_paths' not found. "
 
671
            . "Please get it from any Sandbox installed with version 2.0.15+\n";
 
672
    }
 
673
    if ($already_moved) {
 
674
        if (is_running($new_dir)) {
 
675
            stop_sandbox($new_dir);
 
676
        }
 
677
    }
 
678
    else {
 
679
        stop_sandbox($old_dir);
 
680
        my $result = system qq(mv $old_dir $new_dir) ;
 
681
        if ($result) {
 
682
            croak "unable to move sandbox $old_dir to $new_dir ($OS_ERROR)\n";
 
683
        }
 
684
    }
 
685
    chdir $new_dir;
 
686
    unless ( $old_dir eq $new_dir) {
 
687
        system "./change_paths $old_dir $new_dir";
 
688
    }
 
689
    if ($options{new_port}) {
 
690
        unless ($options{new_port} =~ /^\d+$/) {
 
691
            croak "new port must be numerical ($options{new_port})\n";
 
692
        }
 
693
        if (($options{new_port} <= 1024) or ( $options{new_port} > 32000)) {
 
694
            croak   "new port out of range ($options{new_port}) - "
 
695
                  . "it must be between 1025 and 32000\n";
 
696
        }
 
697
        unless ( -e "$new_dir/change_ports" ) {
 
698
            croak   "script 'change_ports' not found. "
 
699
                . "Please get it from any Sandbox installed with version 2.0.18+\n";
 
700
        }
 
701
        system "./change_ports $options{new_port}";
 
702
    }
 
703
}
 
704
 
 
705
sub stop_sandbox {
 
706
    my ($sbdir) = @_;
 
707
    my ($info) = get_sandbox_params($sbdir);
 
708
    if ( -x "$sbdir/stop" ) {
 
709
        system "$sbdir/stop";
 
710
        my $timeout = 5;
 
711
        while ($timeout && ( -e $info->{opt}{socket} )) {
 
712
            $timeout--;
 
713
            sleep 1;
 
714
        }
 
715
        if ( -e $info->{opt}{socket} ) {
 
716
            croak "sandbox in $sbdir is still running. Unable to stop it\n";
 
717
        }
 
718
    }
 
719
    else {
 
720
        croak "$sbdir does not seem to contain a sandbox\n";
 
721
    }
 
722
}
 
723
 
 
724
sub clone_sandbox_data {
 
725
    my ($source_dir, $dest_dir) = @_;
 
726
    croak "source directory missing\n" unless $source_dir;
 
727
    croak "destination directory missing\n" unless $dest_dir;
 
728
    $source_dir =~ s{/\s*$}{};
 
729
    unless (-d $source_dir) {
 
730
        croak "<$source_dir> is not a valid directory\n";
 
731
    }
 
732
    unless (-d $dest_dir) {
 
733
        croak "<$dest_dir> is not a valid directory\n";
 
734
    }
 
735
    # checking if it is a valid data directory
 
736
    unless (-d "$source_dir/mysql") {
 
737
        croak "<$source_dir> is not a valid data directory\n"
 
738
    }
 
739
    my @pids = glob( "$source_dir/*.pid" );
 
740
    if (@pids) {
 
741
        croak "it seems that your sandbox is running. Please stop it and try again\n";
 
742
    }
 
743
    my @skip_files = map {qr/$_/} (
 
744
        '^relay-log\.info$',
 
745
        '\.err$',
 
746
        '-bin\.\d+$',
 
747
        '-bin\.index$',
 
748
        '-relay-bin\.\d+$',
 
749
        '-relay-bin\.index+$',
 
750
    );
 
751
    find (
 
752
            {
 
753
            no_chdir => 1,
 
754
            wanted   => sub {
 
755
                my $dir   = $File::Find::dir;
 
756
                my $fname = $File::Find::name;
 
757
                $dir =~ s{/$}{};
 
758
                $dir =~ s{.*/}{};
 
759
                $fname =~ s{.*/}{};
 
760
                # print "<$File::Find::name><$File::Find::dir> [$dir] [$fname]\n";
 
761
                return if $dir =~ /^\./;
 
762
                return if $File::Find::name eq $source_dir;
 
763
                for my $skip (@skip_files) {
 
764
                    return if $fname =~ $skip ;
 
765
                }
 
766
                if ( -d  $File::Find::name ) {
 
767
                    if ( -d "$dest_dir/$fname" ) {
 
768
                        return;
 
769
                    }
 
770
                    elsif ( -f "$dest_dir/$fname" ) {
 
771
                        croak "<$dest_dir/$fname> already exists and it is not a directory\n";
 
772
                    }
 
773
                    print_debug( "creating $dest_dir/$fname\n");
 
774
                    my $result = mkdir "$dest_dir/$fname";
 
775
                    unless ($result) {
 
776
                        croak "error creating directory ($!)\n";
 
777
                    }
 
778
                }
 
779
                elsif ( -f $File::Find::name ) {
 
780
                    # print "$Find::File::dir eq $source_dir\n";
 
781
                    if ((! $File::Find::dir) or ($File::Find::dir eq $source_dir)) {
 
782
                        $dir = '';
 
783
                    }
 
784
                    print_debug( "$File::Find::name -> $dest_dir/$dir/$fname\n");
 
785
                    my $result = cp $File::Find::name, "$dest_dir/$dir/$fname";
 
786
                    unless ($result) {
 
787
                        croak "error copying file $!\n";
 
788
                    }
 
789
                }
 
790
                else {
 
791
                    croak "unhandled file $File::Find::name\n";
 
792
                }
 
793
            }
 
794
        },
 
795
        $source_dir
 
796
    );
 
797
}
 
798
 
 
799
sub print_debug {
 
800
    my ($msg, $level) = @_;
 
801
    $level |= 1;
 
802
    if ($DEBUG >= $level) {
 
803
        print $msg;
 
804
    }
 
805
}
 
806
 
 
807
sub is_running {
 
808
    my ($sandbox) = @_;
 
809
    unless ( -d $sandbox ) {
 
810
        confess "Can't see if it's running. <$sandbox> is not a sandbox\n";
 
811
    }
 
812
    my $sboptions = get_sandbox_params($sandbox);
 
813
    unless ($sboptions->{opt} 
 
814
            && $sboptions->{opt}{'pid_file'} 
 
815
            && $sboptions->{opt}{'socket'}) {
 
816
        # print Dumper($sboptions);
 
817
        confess "<$sandbox> is not a single sandbox\n";
 
818
    }
 
819
    if (   ( -f $sboptions->{opt}{'pid_file'} )
 
820
        && ( -e $sboptions->{opt}{'socket'}) ) {
 
821
        return (1, $sboptions);
 
822
    }  
 
823
    else {
 
824
        return (0, $sboptions);
 
825
    }
 
826
}
 
827
 
 
828
sub copy_single_sandbox {
 
829
    my ($source_dir, $dest_dir) = @_;
 
830
    if ($options{new_port}) {
 
831
        croak "option 'new_port' is not supported with 'copy'\n";
 
832
    }
 
833
    unless ( $source_dir) {
 
834
        croak " source directory missing\n";
 
835
    }
 
836
    unless ( -d $source_dir) {
 
837
        croak " <$source_dir> not found\n";
 
838
    }
 
839
    unless ( $dest_dir) {
 
840
        croak " destination directory missing\n";
 
841
    }
 
842
    unless ( -d $dest_dir) {
 
843
        croak " destination directory <$dest_dir> not found\n";
 
844
    }
 
845
    my ($srunning, $ssboptions) = is_running($source_dir);
 
846
    my ($drunning, $dsboptions) = is_running($dest_dir);
 
847
    unless ($ssboptions->{conf}{install_version}) {
 
848
        croak "unable to determine version for <$source_dir>\n";
 
849
    } 
 
850
    unless ($dsboptions->{conf}{install_version}) {
 
851
        croak "unable to determine version for <$dest_dir>\n";
 
852
    } 
 
853
    if ($ssboptions->{conf}{install_version} ne $dsboptions->{conf}{install_version}) {
 
854
        croak "can't copy from $source_dir to $dest_dir. Not the same major version\n";
 
855
    }
 
856
    if ($srunning) {
 
857
        system "$source_dir/stop";
 
858
        if ( -e $ssboptions->{opt}{'pid_file'} ) {
 
859
            system "$source_dir/send_kill";
 
860
        }
 
861
    }
 
862
    if ($drunning) {
 
863
        system "$dest_dir/stop";
 
864
        if ( -e $dsboptions->{opt}{'pid_file'} ) {
 
865
            system "$dest_dir/send_kill";
 
866
        }
 
867
    }
 
868
    clone_sandbox_data( "$source_dir/data", "$dest_dir/data");
 
869
}
 
870
 
 
871
sub is_a_sandbox {
 
872
    my ($dir) = @_;
 
873
    unless ($dir) {
 
874
        confess "directory missing\n";
 
875
    }
 
876
    $dir =~ s{/$}{};
 
877
    my %sandbox_files = map {s{.*/}{}; $_, 1 } glob("$dir/*");
 
878
    my @required = (qw(data start stop send_kill clear use restart), 
 
879
         $sandbox_current_options, $sandbox_options_file );
 
880
    for my $req (@required) {
 
881
        unless (exists $sandbox_files{$req}) {
 
882
            return;
 
883
        }
 
884
    } 
 
885
    return 1;
 
886
}
 
887