ConfigFactory.pm 15.2 KB
Newer Older
unknown's avatar
unknown committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
# -*- cperl -*-
package My::ConfigFactory;

use strict;
use warnings;
use Carp;

use My::Config;
use My::Find;

use File::Basename;


#
# Rules to run first of all
#
my @pre_rules=
(
);


my @share_locations= ("share/mysql", "sql/share", "share");


sub get_basedir {
  my ($self, $group)= @_;
  my $basedir= $group->if_exist('basedir') ||
    $self->{ARGS}->{basedir};
  return $basedir;
}


sub fix_charset_dir {
  my ($self, $config, $group_name, $group)= @_;
  return my_find_dir($self->get_basedir($group),
		     \@share_locations, "charsets");
}

sub fix_language {
  my ($self, $config, $group_name, $group)= @_;
  return my_find_dir($self->get_basedir($group),
		     \@share_locations, "english");
}

sub fix_datadir {
  my ($self, $config, $group_name)= @_;
  my $vardir= $self->{ARGS}->{vardir};
  return "$vardir/$group_name/data";
}

sub fix_pidfile {
  my ($self, $config, $group_name, $group)= @_;
  my $vardir= $self->{ARGS}->{vardir};
  return "$vardir/run/$group_name.pid";
}

sub fix_port {
  my ($self, $config, $group_name, $group)= @_;
  my $hostname= $group->value('#host');
  return $self->{HOSTS}->{$hostname}++;
}

sub fix_host {
  my ($self)= @_;
  # Get next host from HOSTS array
  my @hosts= keys(%{$self->{HOSTS}});;
  my $host_no= $self->{NEXT_HOST}++ % @hosts;
  return $hosts[$host_no];
}

sub is_unique {
  my ($config, $name, $value)= @_;

  foreach my $group ( $config->groups() ) {
    if ($group->option($name)) {
      if ($group->value($name) eq $value){
	return 0;
      }
    }
  }
  return 1;
}

sub fix_server_id {
  my ($self, $config, $group_name, $group)= @_;
#define in the order that mysqlds are listed in my.cnf 

  my $server_id= $group->if_exist('server-id');
  if (defined $server_id){
    if (!is_unique($config, 'server-id', $server_id)) {
      croak "The server-id($server_id) for '$group_name' is not unique";
    }
    return $server_id;
  }

  do {
    $server_id= $self->{SERVER_ID}++;
  } while(!is_unique($config, 'server-id', $server_id));

  #print "$group_name: server_id: $server_id\n";
  return $server_id;
}

sub fix_socket {
  my ($self, $config, $group_name, $group)= @_;
  # Put socket file in tmpdir
107
  my $dir= $self->{ARGS}->{tmpdir};
unknown's avatar
unknown committed
108 109 110
  return "$dir/$group_name.sock";
}

111 112 113 114 115 116
sub fix_tmpdir {
  my ($self, $config, $group_name, $group)= @_;
  my $dir= $self->{ARGS}->{tmpdir};
  return "$dir/$group_name";
}

unknown's avatar
unknown committed
117 118
sub fix_log_error {
  my ($self, $config, $group_name, $group)= @_;
119 120
  my $dir= $self->{ARGS}->{vardir};
  return "$dir/log/$group_name.err";
unknown's avatar
unknown committed
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
}

sub fix_log {
  my ($self, $config, $group_name, $group)= @_;
  my $dir= dirname($group->value('datadir'));
  return "$dir/mysqld.log";
}

sub fix_log_slow_queries {
  my ($self, $config, $group_name, $group)= @_;
  my $dir= dirname($group->value('datadir'));
  return "$dir/mysqld-slow.log";
}

sub fix_secure_file_priv {
  my ($self)= @_;
  my $vardir= $self->{ARGS}->{vardir};
  # By default, prevent the started mysqld to access files outside of vardir
  return $vardir;
}

sub fix_std_data {
  my ($self, $config, $group_name, $group)= @_;
  my $basedir= $self->get_basedir($group);
  return "$basedir/mysql-test/std_data";
}

148 149 150 151 152
sub ssl_supported {
  my ($self)= @_;
  return $self->{ARGS}->{ssl};
}

Magnus Svensson's avatar
Magnus Svensson committed
153 154 155 156 157 158 159
sub fix_skip_ssl {
  return if !ssl_supported(@_);
  # Add skip-ssl if ssl is supported to avoid
  # that mysqltest connects with SSL by default
  return 1;
}

unknown's avatar
unknown committed
160
sub fix_ssl_ca {
161
  return if !ssl_supported(@_);
unknown's avatar
unknown committed
162 163 164 165 166
  my $std_data= fix_std_data(@_);
  return "$std_data/cacert.pem"
}

sub fix_ssl_server_cert {
167
  return if !ssl_supported(@_);
unknown's avatar
unknown committed
168 169 170 171 172
  my $std_data= fix_std_data(@_);
  return "$std_data/server-cert.pem"
}

sub fix_ssl_client_cert {
173
  return if !ssl_supported(@_);
unknown's avatar
unknown committed
174 175 176 177 178
  my $std_data= fix_std_data(@_);
  return "$std_data/client-cert.pem"
}

sub fix_ssl_server_key {
179
  return if !ssl_supported(@_);
unknown's avatar
unknown committed
180 181 182 183 184
  my $std_data= fix_std_data(@_);
  return "$std_data/server-key.pem"
}

sub fix_ssl_client_key {
185
  return if !ssl_supported(@_);
unknown's avatar
unknown committed
186 187 188 189 190 191 192 193 194 195 196 197
  my $std_data= fix_std_data(@_);
  return "$std_data/client-key.pem"
}


#
# Rules to run for each mysqld in the config
#  - will be run in order listed here
#
my @mysqld_rules=
  (
 { 'basedir' => sub { return shift->{ARGS}->{basedir}; } },
198
 { 'tmpdir' => \&fix_tmpdir },
unknown's avatar
unknown committed
199 200 201 202 203 204 205
 { 'character-sets-dir' => \&fix_charset_dir },
 { 'language' => \&fix_language },
 { 'datadir' => \&fix_datadir },
 { 'pid-file' => \&fix_pidfile },
 { '#host' => \&fix_host },
 { 'port' => \&fix_port },
 { 'socket' => \&fix_socket },
206
 { '#log-error' => \&fix_log_error },
207 208 209 210
 { 'general_log' => 1 },
 { 'general_log_file' => \&fix_log },
 { 'slow_query_log' => 1 },
 { 'slow_query_log_file' => \&fix_log_slow_queries },
unknown's avatar
unknown committed
211 212 213 214 215
 { '#user' => sub { return shift->{ARGS}->{user} || ""; } },
 { '#password' => sub { return shift->{ARGS}->{password} || ""; } },
 { 'server-id' => \&fix_server_id, },
 # By default, prevent the started mysqld to access files outside of vardir
 { 'secure-file-priv' => sub { return shift->{ARGS}->{vardir}; } },
216 217 218
 { 'ssl-ca' => \&fix_ssl_ca },
 { 'ssl-cert' => \&fix_ssl_server_cert },
 { 'ssl-key' => \&fix_ssl_server_key },
unknown's avatar
unknown committed
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
  );


sub fix_ndb_mgmd_port {
  my ($self, $config, $group_name, $group)= @_;
  my $hostname= $group->value('HostName');
  return $self->{HOSTS}->{$hostname}++;
}


sub fix_cluster_dir {
  my ($self, $config, $group_name, $group)= @_;
  my $vardir= $self->{ARGS}->{vardir};
  my (undef, $process_type, $idx, $suffix)= split(/\./, $group_name);
  return "$vardir/mysql_cluster.$suffix/$process_type.$idx";
}


sub fix_cluster_backup_dir {
  my ($self, $config, $group_name, $group)= @_;
  my $vardir= $self->{ARGS}->{vardir};
  my (undef, $process_type, $idx, $suffix)= split(/\./, $group_name);
  return "$vardir/mysql_cluster.$suffix/";
}


#
# Rules to run for each ndb_mgmd in the config
#  - will be run in order listed here
#
my @ndb_mgmd_rules=
(
 { 'PortNumber' => \&fix_ndb_mgmd_port },
 { 'DataDir' => \&fix_cluster_dir },
);


#
# Rules to run for each ndbd in the config
#  - will be run in order listed here
#
my @ndbd_rules=
(
 { 'HostName' => \&fix_host },
 { 'DataDir' => \&fix_cluster_dir },
 { 'BackupDataDir' => \&fix_cluster_backup_dir },
);


#
# Rules to run for each cluster_config section
#  - will be run in order listed here
#
my @cluster_config_rules=
(
 { 'ndb_mgmd' => \&fix_host },
 { 'ndbd' => \&fix_host },
 { 'mysqld' => \&fix_host },
 { 'ndbapi' => \&fix_host },
);


#
# Rules to run for [client] section
#  - will be run in order listed here
#
my @client_rules=
(
);


#
# Rules to run for [mysqltest] section
#  - will be run in order listed here
#
my @mysqltest_rules=
(
296 297 298
 { 'ssl-ca' => \&fix_ssl_ca },
 { 'ssl-cert' => \&fix_ssl_client_cert },
 { 'ssl-key' => \&fix_ssl_client_key },
Magnus Svensson's avatar
Magnus Svensson committed
299
 { 'skip-ssl' => \&fix_skip_ssl },
unknown's avatar
unknown committed
300 301 302 303 304 305 306 307 308 309 310 311 312
);


#
# Rules to run for [mysqlbinlog] section
#  - will be run in order listed here
#
my @mysqlbinlog_rules=
(
 { 'character-sets-dir' => \&fix_charset_dir },
);


313 314 315 316 317 318 319 320 321 322
#
# Rules to run for [mysql_upgrade] section
#  - will be run in order listed here
#
my @mysql_upgrade_rules=
(
 { 'tmpdir' => sub { return shift->{ARGS}->{tmpdir}; } },
);


unknown's avatar
unknown committed
323
#
unknown's avatar
unknown committed
324
# Generate a [client.<suffix>] group to be
unknown's avatar
unknown committed
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
# used for connecting to [mysqld.<suffix>]
#
sub post_check_client_group {
  my ($self, $config, $client_group_name, $mysqld_group_name)= @_;

  #  Settings needed for client, copied from its "mysqld"
  my %client_needs=
    (
     port       => 'port',
     socket     => 'socket',
     host       => '#host',
     user       => '#user',
     password   => '#password',
    );

  my $group_to_copy_from= $config->group($mysqld_group_name);
  while (my ($name_to, $name_from)= each( %client_needs )) {
    my $option= $group_to_copy_from->option($name_from);

    if (! defined $option){
      #print $config;
      croak "Could not get value for '$name_from'";
    }
    $config->insert($client_group_name, $name_to, $option->value())
  }
}


sub post_check_client_groups {
 my ($self, $config)= @_;

 my $first_mysqld= $config->first_like('mysqld.');

 return unless $first_mysqld;

 # Always generate [client] pointing to the first
 # [mysqld.<suffix>]
 $self->post_check_client_group($config,
				'client',
				$first_mysqld->name());

 # Then generate [client.<suffix>] for each [mysqld.<suffix>]
 foreach my $mysqld ( $config->like('mysqld.') ) {
   $self->post_check_client_group($config,
				  'client'.$mysqld->after('mysqld'),
				  $mysqld->name())
 }

}


unknown's avatar
unknown committed
376 377
#
# Generate [embedded] by copying the values
378 379
# needed from the default [mysqld] section
# and from first [mysqld.<suffix>]
unknown's avatar
unknown committed
380 381
#
sub post_check_embedded_group {
382
  my ($self, $config)= @_;
unknown's avatar
unknown committed
383

384
  return unless $self->{ARGS}->{embedded};
unknown's avatar
unknown committed
385

386 387 388
  my $mysqld= $config->group('mysqld') or
    croak "Can't run with embedded, config has no default mysqld section";

389 390 391 392 393
  my $first_mysqld= $config->first_like('mysqld.') or
    croak "Can't run with embedded, config has no mysqld";

  my @no_copy =
    (
394
     '#log-error', # Embedded server writes stderr to mysqltest's log file
395
     'slave-net-timeout', # Embedded server are not build with replication
396
    );
unknown's avatar
unknown committed
397

398
  foreach my $option ( $mysqld->options(), $first_mysqld->options() ) {
399 400 401
    # Don't copy options whose name is in "no_copy" list
    next if grep ( $option->name() eq $_, @no_copy);

unknown's avatar
unknown committed
402 403 404 405 406 407
    $config->insert('embedded', $option->name(), $option->value())
  }

}


unknown's avatar
unknown committed
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
sub resolve_at_variable {
  my ($self, $config, $group, $option)= @_;

  # Split the options value on last .
  my @parts= split(/\./, $option->value());
  my $option_name= pop(@parts);
  my $group_name=  join('.', @parts);

  $group_name =~ s/^\@//; # Remove at

  my $from_group= $config->group($group_name)
    or croak "There is no group named '$group_name' that ",
      "can be used to resolve '$option_name'";

  my $from= $from_group->value($option_name);
  $config->insert($group->name(), $option->name(), $from)
}


sub post_fix_resolve_at_variables {
  my ($self, $config)= @_;

  foreach my $group ( $config->groups() ) {
    foreach my $option ( $group->options()) {
      next unless defined $option->value();

      $self->resolve_at_variable($config, $group, $option)
	if ($option->value() =~ /^\@/);
    }
  }
}

sub post_fix_mysql_cluster_section {
  my ($self, $config)= @_;

  # Add a [mysl_cluster.<suffix>] section for each
  # defined [cluster_config.<suffix>] section
445
  foreach my $group ( $config->like('cluster_config\.\w*$') )
unknown's avatar
unknown committed
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
  {
    my @urls;
    # Generate ndb_connectstring for this cluster
    foreach my $ndb_mgmd ( $config->like('cluster_config.ndb_mgmd.')) {
      if ($ndb_mgmd->suffix() eq $group->suffix()) {
	my $host= $ndb_mgmd->value('HostName');
	my $port= $ndb_mgmd->value('PortNumber');
	push(@urls, "$host:$port");
      }
    }
    croak "Could not generate valid ndb_connectstring for '$group'"
      unless @urls > 0;
    my $ndb_connectstring= join(";", @urls);

    # Add ndb_connectstring to [mysql_cluster.<suffix>]
    $config->insert('mysql_cluster'.$group->suffix(),
		    'ndb_connectstring', $ndb_connectstring);

    # Add ndb_connectstring to each mysqld connected to this
    # cluster
    foreach my $mysqld ( $config->like('cluster_config.mysqld.')) {
      if ($mysqld->suffix() eq $group->suffix()) {
	my $after= $mysqld->after('cluster_config.mysqld');
	$config->insert("mysqld$after",
			'ndb_connectstring', $ndb_connectstring);
      }
    }
  }
}

#
# Rules to run last of all
#
my @post_rules=
(
 \&post_check_client_groups,
 \&post_fix_mysql_cluster_section,
 \&post_fix_resolve_at_variables,
unknown's avatar
unknown committed
484
 \&post_check_embedded_group,
unknown's avatar
unknown committed
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
);


sub run_rules_for_group {
  my ($self, $config, $group, @rules)= @_;
  foreach my $hash ( @rules ) {
    while (my ($option, $rule)= each( %{$hash} )) {
      # Only run this rule if the value is not already defined
      if (!$config->exists($group->name(), $option)) {
	my $value;
	if (ref $rule eq "CODE") {
	  # Call the rule function
	  $value= &$rule($self, $config, $group->name(),
			 $config->group($group->name()));
	} else {
	  $value= $rule;
	}
	if (defined $value) {
	  $config->insert($group->name(), $option, $value, 1);
	}
      }
    }
  }
}


sub run_section_rules {
  my ($self, $config, $name, @rules)= @_;

  foreach my $group ( $config->like($name) ) {
    $self->run_rules_for_group($config, $group, @rules);
  }
}


sub run_generate_sections_from_cluster_config {
  my ($self, $config)= @_;

  my @options= ('ndb_mgmd', 'ndbd',
		'mysqld', 'ndbapi');

526
  foreach my $group ( $config->like('cluster_config\.\w*$') ) {
unknown's avatar
unknown committed
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608

    # Keep track of current index per process type
    my %idxes;
    map { $idxes{$_}= 1; } @options;

    foreach my $option_name ( @options ) {
      my $value= $group->value($option_name);
      my @hosts= split(/,/, $value, -1); # -1 => return also empty strings

      # Add at least one host
      push(@hosts, undef) unless scalar(@hosts);

      # Assign hosts unless already fixed
      @hosts= map { $self->fix_host() unless $_; } @hosts;

      # Write the hosts value back
      $group->insert($option_name, join(",", @hosts));

      # Generate sections for each host
      foreach my $host ( @hosts ){
	my $idx= $idxes{$option_name}++;

	my $suffix= $group->suffix();
	# Generate a section for ndb_mgmd to read
	$config->insert("cluster_config.$option_name.$idx$suffix",
			"HostName", $host);

	if ($option_name eq 'mysqld'){
	  my $datadir=
	    $self->fix_cluster_dir($config,
				   "cluster_config.mysqld.$idx$suffix",
				   $group);
	  $config->insert("mysqld.$idx$suffix",
			  'datadir', "$datadir/data");
	}
      }
    }
  }
}


sub new_config {
  my ($class, $args)= @_;

  my @required_args= ('basedir', 'baseport', 'vardir', 'template_path');

  foreach my $required ( @required_args ) {
    croak "you must pass '$required'" unless defined $args->{$required};
  }

  # Fill in hosts/port hash
  my $hosts= {};
  my $baseport= $args->{baseport};
  $args->{hosts}= [ 'localhost' ] unless exists($args->{hosts});
  foreach my $host ( @{$args->{hosts}} ) {
     $hosts->{$host}= $baseport;
  }

  # Open the config template
  my $config= My::Config->new($args->{'template_path'});
  my $extra_template_path= $args->{'extra_template_path'};
  if ($extra_template_path){
    $config->append(My::Config->new($extra_template_path));
  }
  my $self= bless {
		   CONFIG       => $config,
		   ARGS         => $args,
		   HOSTS        => $hosts,
		   NEXT_HOST    => 0,
		   SERVER_ID    => 1,
		  }, $class;


  {
    # Run pre rules
    foreach my $rule ( @pre_rules ) {
      &$rule($self, $config);
    }
  }


  $self->run_section_rules($config,
609
			   'cluster_config\.\w*$',
unknown's avatar
unknown committed
610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
			   @cluster_config_rules);
  $self->run_generate_sections_from_cluster_config($config);

  $self->run_section_rules($config,
			   'cluster_config.ndb_mgmd.',
			   @ndb_mgmd_rules);
  $self->run_section_rules($config,
			   'cluster_config.ndbd',
			   @ndbd_rules);

  $self->run_section_rules($config,
			   'mysqld.',
			   @mysqld_rules);

  # [mysqlbinlog] need additional settings
  $self->run_rules_for_group($config,
			     $config->insert('mysqlbinlog'),
			     @mysqlbinlog_rules);

629 630 631 632 633
  # [mysql_upgrade] need additional settings
  $self->run_rules_for_group($config,
			     $config->insert('mysql_upgrade'),
			     @mysql_upgrade_rules);

unknown's avatar
unknown committed
634
  # Additional rules required for [client]
unknown's avatar
unknown committed
635 636 637 638 639
  $self->run_rules_for_group($config,
			     $config->insert('client'),
			     @client_rules);


unknown's avatar
unknown committed
640
  # Additional rules required for [mysqltest]
unknown's avatar
unknown committed
641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657
  $self->run_rules_for_group($config,
			     $config->insert('mysqltest'),
			     @mysqltest_rules);

  {
    # Run post rules
    foreach my $rule ( @post_rules ) {
      &$rule($self, $config);
    }
  }

  return $config;
}


1;