# # Copyright (c) 2001-2016 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @summary Library for performing operations on NateDB ## @author dl-nacl-dev@netapp.com ## @status shared ## @pod here =head1 NAME NACL::Database::MYSQL =head1 DESCRIPTION This library contains methods for performing database operations on Nate database server,which is specific for NACL APIs. It is derived from NATE::Database::MYSQL and hence it supports only MySQL. =cut package NACL::Database::MYSQL; use strict; use warnings; use parent qw(NATE::Database::MYSQL NACL::Database); use NATE::Log qw(log_global); use Storable qw(nfreeze thaw); use Compress::Zlib qw(compress uncompress); use Params::Validate qw(validate validate_with :types); use Data::Dumper qw(Dumper); use NATE::ParamSet; use NATE::Events qw(call_on_fork_add); use NATE::BaseException qw(:try); use NATE::Exceptions::DBDuplicateEntry; my $Log; $Log = log_global(); my $may_enter = $Log->may_enter(); my $may_exit = $Log->may_exit(); call_on_fork_add({callback => sub { $Log = log_global(); }, keep_on_fork => 1}); call_on_fork_add({callback => sub { __PACKAGE__->db_forked() }, keep_on_fork => 1}); use Class::MethodMaker [ scalar => [ {'-type' => 'NACL::Database::MYSQL', '-static' => 1, }, 'dbh' ], scalar => '_nacl_tables_created' ]; =head2 new Instantiates a new NACL::Database object. It creates a singleton connection to Nate database server and creates the required internal tables for NACL =over =item Synopsis my $dbh = NACL::Database::MYSQL->new(); (or) my $dbh = NACL::Cleanup::MYSQL->new( database => 'test' ); =item Arguments =over =item C<< driver >> (Optional) Name of the db driver. Defaults to NATE_DB_DRIVER set by ntest. =item C<< database >> (Optional) Name of the datbase. Defaults to the name obtained by replacing 'test' with 'nacl' on the value set on NATE_DB_NAME by ntest =item C<< username >> (Optional) User name for connecting to db. Defaults to NATE_DB_USER set by ntest. =item C<< password >> (Optional) Password for connecting to db. Defaults to NATE_DB_PASSWORD set by ntest. =item C<< host >> (Optional) Name of the host on which dbserver is running. Defaults to NATE_DB_HOST set by ntest. =item C<< port >> (Optional) Name of the port on which dbserver is running. Defaults to NATE_DB_PORT set by ntest. =back =back =cut sub new { my ($pkg, @args) = @_; my $var = NATE::ParamSet->new(global=>1); my (%opts, $dbh); if ( $pkg->dbh_isset() ) { $dbh = $pkg->dbh(); return $dbh; } else { %opts = validate(@args, { driver => {type=>SCALAR|UNDEF, default => $var->get('NATE_DB_DRIVER')}, database => {type=>SCALAR, default => _nacl_db_name() }, username => {type=>SCALAR|UNDEF, default => $var->get('NATE_DB_USER')}, password => {type=>SCALAR|UNDEF, default => $var->get('NATE_DB_PASSWORD')}, host => {type=>SCALAR|UNDEF, default => $var->get('NATE_DB_HOST')}, port => {type=>SCALAR|UNDEF, default => $var->get('NATE_DB_PORT')}, data_dir => {type=>SCALAR|UNDEF, default => $var->get('NATE_DB_DIR_TEMP')||$var->get('NATE_DB_DIR')||''}, trace => {type=>SCALAR|UNDEF, default => $var->get('NATE_DB_TRACE') || 0 } } ); if ( defined $opts{database}) { eval { $dbh = NATE::Database::MYSQL->new(%opts); }; if ( $@ ) { $Log->comment("Unable to create db: ".$@) } else { bless $dbh, $pkg; $pkg->dbh($dbh); # #If _nacl_tables_created attribute is already set #within the same process # or #If the system_control table is already created by # another ntest process, then it is not required # to call _create_nacl_tables # if( !$dbh->_nacl_tables_created_isset() && !$dbh->_system_control_exists() ) { $dbh->_create_nacl_tables(); } } } return $dbh; } } sub _system_control_exists { $Log->enter() if $may_enter; my $dbh = shift; my $result = 1; eval { $dbh->db_getone(query => "select logdir from system_control");; }; if ( $@ ) { $result = 0; } $Log->exit() if $may_exit; return $result; } sub _nacl_db_name { $Log->enter() if $may_enter; my $var = NATE::ParamSet->new(global=>1); my $nacl_db_name = $var->get('NATE_DB_NAME'); $nacl_db_name =~ s/test/nacl/ if(defined $nacl_db_name); $Log->exit() if $may_exit; return $nacl_db_name; } sub _create_nacl_tables { $Log->enter() if $may_enter; my $nacl_db = shift; my @create_ddl = ( "create table if not exists command_ds (xml_file varchar(500) not null, ds LONGBLOB ) engine=InnoDB", "create table if not exists cli_zapi_ds (build_root varchar(500) not null unique, ds LONGBLOB) engine=InnoDB", # CDEFs table has two columns: path (which is the primary # key), and the data-structure containing the CDEFs # Do NOT make "path" an index because for long paths it causes the # insert to take a really long time. See burt 945467. 'create table if not exists cdefs (path varchar(500) not null unique, ds LONGBLOB ) engine=InnoDB', 'create table if not exists zapi_cdefs (path varchar(500) not null unique, ds LONGBLOB ) engine=InnoDB', "create table if not exists system_control (logdir varchar(500) not null, unique(logdir) ) engine=InnoDB", "create table if not exists uichanges (build_path varchar(500) not null unique, ds LONGBLOB ) engine=InnoDB", "create table if not exists version_compatibility (build_path varchar(500) not null unique, ds LONGBLOB ) engine=InnoDB", ); foreach my $create ( @create_ddl ) { eval { $nacl_db->db_do(query => $create); }; if ( $@ && $@ !~ /Duplicate/i ) { $Log->comment("\n query: $create failed to execute" . " errors: $@"); } } my $var = NATE::ParamSet->new(global => 1); my $log_path = $var->get('TOP_LOGDIR'); my $row = { logdir => $log_path }; $nacl_db->_db_insert_ignore_duplicate_entry(table => "system_control", binds => $row); $nacl_db->_nacl_tables_created(1); $Log->exit() if $may_exit; } # # Private function which queries the table containing dump, if the # table doesnt exists it creates it. # sub _query_component_ds { $Log->enter() if $may_enter; my $nacl_db = shift; my %opts = validate_with( params => \@_, spec => { xml_file => { type => SCALAR } },); my $select_query = "select ds from command_ds where xml_file =\"$opts{xml_file}\""; my $ds = $nacl_db->_query_table(query => $select_query); $Log->exit() if $may_exit; return $ds; } sub _query_cdefs_table { $Log->enter() if $may_enter; my ($self, @args) = @_; my %opts = validate_with( params => \@args, spec => {path => {type => SCALAR}}, ); my $select_query = 'select ds from cdefs where path ="' . $opts{path} . '"'; my $ds = $self->_query_table(query => $select_query); $Log->exit() if $may_exit; return $ds; } sub _query_zapi_cdefs_table { $Log->enter() if $may_enter; my ($self, @args) = @_; my %opts = validate_with( params => \@args, spec => {path => {type => SCALAR}}, ); my $select_query = 'select ds from zapi_cdefs where path ="' . $opts{path} . '"'; my $ds = $self->_query_table(query => $select_query); $Log->exit() if $may_exit; return $ds; } # # Private function which inserts a ds to the table # sub _insert_table { $Log->enter() if $may_enter; my ($nacl_db, %opts) = @_; $Log->comment("Initiating _insert_table_compress_freeze subroutine"); $nacl_db->_insert_table_compress_freeze(%opts); $Log->comment("Completed _insert_table_compress_freeze subroutine"); $Log->exit() if $may_exit; } sub _query_table { $Log->enter() if $may_enter; my ($nacl_db, %opts) = @_; $Log->comment("Initiating _query_table_thaw_uncompress subroutine"); my $ds = $nacl_db->_query_table_thaw_uncompress(%opts); $Log->comment("Completed _query_table_thaw_uncompress subroutine"); $Log->exit() if $may_exit; return $ds; } # # Private function which queries the table containing cli to zapi mapping, if the # table doesnt exists it creates it. # sub _query_cli_zapi { $Log->enter() if $may_enter; my $nacl_db = shift; my %opts = validate_with( params => \@_, spec => { build_root => { type => SCALAR } },); my ($row, $ds); my $select_query = "select ds from cli_zapi_ds where build_root =\"$opts{build_root}\""; $ds = $nacl_db->_query_table(query => $select_query); $Log->exit() if $may_exit; return $ds; } sub _query_uichanges { $Log->enter() if $may_enter; my $nacl_db = shift; my %opts = validate_with( params => \@_, spec => { build_path => { type => SCALAR } },); my ($row, $ds); my $select_query = "select ds from uichanges where build_path =\"$opts{build_path}\""; $ds = $nacl_db->_query_table(query => $select_query); $Log->exit() if $may_exit; return $ds; } sub _query_version_compatibility { $Log->enter() if $may_enter; my $nacl_db = shift; my %opts = validate_with( params => \@_, spec => { build_path => { type => SCALAR } },); my ($row, $ds); my $select_query = "select ds from version_compatibility where build_path =\"$opts{build_path}\""; $ds = $nacl_db->_query_table(query => $select_query); $Log->exit() if $may_exit; return $ds; } # # The following function gets called when it is forked # sub db_forked { my $pkg = shift; if($pkg->dbh_isset()) { my $handle = $pkg->dbh(); $handle->{dbh}->{InactiveDestroy} = 1; $pkg->dbh_reset(); } } sub _update_table { $Log->enter() if $may_enter; my $nacl_db = shift; my %opts = validate_with( params => \@_, spec => { row => { type => HASHREF }, table_name => { type => SCALAR }, }); my $table_name = $opts{table_name}; $opts{row}->{ds} = compress(nfreeze($opts{row}->{ds})); my $xml_file = delete $opts{row}->{xml_file}; eval { $nacl_db->db_update(table => $table_name, binds => $opts{row}, cond => "where xml_file = \"".$xml_file."\""); $nacl_db->db_commit(); }; if ( $@ ) { my $error = $@; $Log->comment("\n db_update failed for ". Dumper($opts{row}) . " errors $error"); } $Log->exit() if $may_exit; } 1;