# # Copyright (c) 2013 NetApp Inc. # All rights reserved. # ## @summary Provide a common set of libraries to support setup and cleanup on controllers ## @author dl-nacl-dev ## @status private ## @pod here =head1 NAME SetupCleanup =head1 DESCRIPTION SetupCleanup - Is a framework used to Setup and Cleanup of your testing environment. It has the ability to take in XML that describes your environment and setup/cleanup that environment for your testing purposes. =cut package NACL::AppBuilder::SetupCleanup; # Compiler directives. use strict; use warnings; # NATE and Standard Module import use NATE::Log qw(log_global); my $Log = log_global(); my $may_enter = $Log->may_enter(); my $may_exit = $Log->may_exit(); use Data::Dumper; use File::Basename; use NATE::Util::DataTypes qw(TRUE FALSE); use Cwd; use NATE::ProcManager; use NATE::Events; use NATE::Inc qw(find_file); use Time::HiRes; # Exceptions use NATE::BaseException qw(:try); use NATE::Exceptions::Argument qw(:try); use Params::Validate qw(OBJECT SCALAR SCALARREF ARRAYREF HASHREF UNDEF GLOBREF); # Load EnGenX and standard modules use NACL::AppBuilder::EnGenX::Resource; use NACL::AppBuilder::EnGenX::ResourceList; use NACL::AppBuilder::SC::Controller (); use NACL::AppBuilder::EnGenX::Util; use ParamsMgr; use SubIPC; # NACL libraries in use use NACL::C::Node; use NACL::C::Volume; use vars qw(@ISA @DTD_SCHEMA_FILES $ENGENX_DTD_PATH $SANMGR_DTD_PATH); @ISA = qw(NACL::AppBuilder::EnGenX::Util); # Obtain the EnGenX DTD Path $ENGENX_DTD_PATH = &_get_dtd_path(); $SANMGR_DTD_PATH = &_get_sanmgr_dtd_path(); # Internal DTD XML Schema files for EnGenX @DTD_SCHEMA_FILES = ( $ENGENX_DTD_PATH . "/scenario.dtd", $SANMGR_DTD_PATH . "/clients.dtd", $ENGENX_DTD_PATH . "/clusters.dtd", $SANMGR_DTD_PATH . "/hypervisors.dtd", $SANMGR_DTD_PATH . "/fabrics.dtd", $SANMGR_DTD_PATH . "/eventman.dtd" ); # Exported global variables ########################### $NACL::AppBuilder::EnGenX::TRUE = 1; $NACL::AppBuilder::EnGenX::FALSE = 0; # Exported global variables ########################### $SanMgr::TRUE = 1; $SanMgr::FALSE = 0; ############################################################################### # Public Methods ############################################################################### =head1 METHODS =head2 new Create a SetupCleanup object and parse the given XML into a ResourceList. my $SetupCleanup_obj = NACL::AppBuilder::SetupCleanup->new( file => $Xml_file,); $SetupCleanup_obj->config(); $SetupCleanup_obj->setup(); =over =item Options =over =item C<< xml_file=>$xml_file >> (Required) An XML file that describes the environment to generate =item C<< schema_files=>@my_dtd_files >> (Optional) Optional ARRAY of paths to DTD files to be used to validate the XML Defaults to files in the lib/NACL/AppBuilder/SetupCleanup/SC/DTDFiles directory. =item C<< load_resources=>0|1 >> (Optional) If set will create objects early. This is used for SanClient and SanSwitch, etc. since creating these objects will load the resources. Eventually as we migrate to NACL this argument will be unused. Defaults to 1 =item C<< run_id_prefix=>"my_test_string" >> (Optional) String that will be be used as a prefix for subtest run id strings. Defaults to an empty string. =item C<< enable_subtest_count=>0|1 >> (Optional) If set, subtest run id's have an ever increasing integer suffix Defaults to 1 =item C<< display_xml=>0|1 >> (Optional) If set,the XML file contents are displayed in the logs Defaults to 1 =back =item Return The an object created =item Exceptions NATE::BaseException NATE::Exceptions::Argument =cut ######################################################################## sub new { $Log->enter() if $may_enter; my ($proto, @opts) = @_; my %args = (); my $self = {}; my $class = ref($proto) || $proto; bless($self, $class); # Process arguments. try { %args = Params::Validate::validate( @opts, { file => { type => SCALAR, optional => TRUE, default => undef, }, nodes => { type => ARRAYREF, optional => TRUE, default => undef, }, clusters => { type => ARRAYREF, optional => TRUE, default => undef, }, xml_file => { type => SCALAR, optional => TRUE, default => "", }, resource_list => { type => HASHREF, optional => TRUE, default => undef, }, configbroker_group => { type => SCALAR, optional => TRUE, default => "", }, # Common Arguments schema_files => { type => ARRAYREF, optional => TRUE, default => [], }, load_resources => { type => SCALAR, optional => TRUE, default => TRUE, }, run_id_prefix => { type => SCALAR, optional => TRUE, default => "", }, enable_subtest_count => { type => SCALAR, optional => TRUE, default => TRUE, }, display_xml => { type => SCALAR, optional => TRUE, default => TRUE, }, display_xml_runid => { type => SCALAR, optional => TRUE, default => "XML-environment", }, obj_creation_timeout => { type => SCALAR, optional => TRUE, default => "7200" }, # NEW ARGUMENTs debug_level => { type => SCALAR, optional => TRUE, default => '1', }, } ); } otherwise { my $xcptn = shift; $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw($xcptn->text()); }; # if nodes is passed in, save for load_resources reuse $self->{nodes} = delete $args{nodes} if defined $args{nodes}; # if clusters is passed in, save for load_resources reuse $self->{clusters} = delete $args{clusters} if defined $args{clusters}; # replace -file with -xml_file if (defined $args{file}) { $args{xml_file} = delete $args{file}; } #Assign Values my $environment = {}; my $xml_file = $args{xml_file}; my $cb_group = $args{configbroker_group}; my $load_resources = $args{load_resources}; # Set the run_id_prefix $self->{run_id_prefix} = $args{run_id_prefix}; $self->{subtest_count} = 1; $self->{enable_subtest_count} = $args{enable_subtest_count}; $self->{obj_creation_timeout} = $args{obj_creation_timeout}; $self->{resources_loaded} = FALSE; # allow for error stack build ups $self->{error_dont_overwrite_errors} = TRUE; $self->{error_stack_style} = "normal"; # get a handle to the resource group from cb_group(config broker db) #if ($cb_group ne "") { if ((defined $cb_group) && ($cb_group ne "")) { $self->{rgname} = $cb_group; $self->{cbh} = new ConfigBroker(); $self->{rgh} = $self->{cbh}->get_resource_group_object($self->{rgname}); } # If user provided an XML file if ($xml_file ne "") { # get the filepath of the xml_file $xml_file = $self->_get_xml_path($xml_file); # Initialize the resource list. $self->{resource_list} = new NACL::AppBuilder::EnGenX::ResourceList(); $Log->comment("Loading XML file $xml_file"); # Before we load the new XML files, check to see if we need to clear # out the ParamSet on ParamsMgr if ($args{clear_paramsmgr}) { &ParamsMgr::clear_global_paramset(); if (&ParamsMgr::error()) { $Log->exit() if $may_exit; my $errmsg = join "\n", &ParamsMgr::get_error(); NATE::BaseException->throw( "Failed to clear ParamsMgr: $errmsg"); } } # Process and Validate the xml file and any override schema files $self->load_xml( -xml_files => [$xml_file], -schema_files => $args{schema_files}, -display_xml => $args{display_xml}, -display_xml_runid => $args{display_xml_runid}, ); # TODO: Try to change the following logic for something equivalent using # Params::Validate. # Get the set of environment parameters from ParamsMgr &ParamsMgr::process_args(%args); $environment = &ParamsMgr::search(search_path => [], key => "ENVIRONMENT"); if (&ParamsMgr::error()) { $Log->exit() if $may_exit; my $errmsg = join "\n", &ParamsMgr::get_error(); NATE::Exceptions::Argument->throw($errmsg); } # # End TODO } # If user provided a ResourceList Object elsif (defined($args{resource_list})) { $self->{resource_list} = $args{resource_list}; $environment->{DEBUG_LEVEL} = $args{debug_level}; } else { NATE::BaseException->throw( "NACL::AppBuilder::SetupCleanup::new(): You must provide either a path to an XML file or a " . "NACL::AppBuilder::EnGenX::ResouerceList object. Could not find either from the " . "provided params \n" . Dumper(%args)); } # TODO: In the future when we are only using Params::Validate, we can # stop setting the ParamsMgr::debug_level parameter, we could delete # that line all together, because there will be no class or methos # using ParamasMgr. # Also, verify if SubIpc is even used. We are using instead # NACL::AppBuilder::EnGenX:: Subprocess, but SanClient or other SanMgr-inherited classes # could be using it. # Save the debug level and set it in the objects my $dbg_level = defined($environment->{DEBUG_LEVEL}) ? $environment->{DEBUG_LEVEL} : $args{debug_level}; # Set the $Log accodingly. if ($Log->may_trace()) { $dbg_level = 3; } elsif ($Log->may_debug()) { $dbg_level = 7; } # Set the internal variable value. $self->debug_level(-debug_level => $dbg_level); # Set the debug level on ParamsMgr and SubIPC &ParamsMgr::debug_level(-debug_level => $dbg_level); if (&ParamsMgr::error()) { $Log->exit() if $may_exit; my $errmsg = join "\n", &ParamsMgr::get_error(); NATE::BaseException->throw( "Failed to set debug level for ParamsMgr: $errmsg"); } &SubIPC::debug_level(-debug_level => $dbg_level); if (&SubIPC::error()) { $Log->exit() if $may_exit; my $errmsg = join "\n", &SubIPC::get_error(); NATE::BaseException->throw( "Failed to set debug level for SubIPC: $errmsg"); } # # End TODO # Create local objects for each of the resource types # these are to be used later for config, setup, validate and cleanup work. $self->{sm_controller_object} = NACL::AppBuilder::SC::Controller->_new(-parent => $self); # If load_resources is set, load up objects for the resources if ($load_resources == 1) { $self->load_resources(%args); } else { $Log->comment("No resource objects loaded load_resources => 0"); } # Translate the XML stored by the ParamsMgr to a Resource list if (length($xml_file)) { $self->xml_to_list(); } # delete the larger hash that was stored for time efficiency # because it took alot of time to build for large configs. delete $self->{vserver_client_relationship_hash}; $Log->exit() if $may_exit; return $self; } ## end sub new =head2 setup Setup each resource type (controllers, clients, switches, etc.) $SetupCleanup_obj->setup(); =over =item Options TBD =over =item C<< TBD >> TBD =back =item Return TBD =item Exceptions BaseException =cut ############################################################################### sub setup { my ($self, %args) = @_; &SubIPC::remove_subtest(); my ($st_status, $subtest, $controller_objs); my (@messages) = (); # Get the set of actions parameters from ParamsMgr my $actions = &ParamsMgr::search(search_path => [], key => "ACTIONS"); if (&ParamsMgr::error()) { my $errmsg = &ParamsMgr::get_error(); NATE::BaseException->throw( "Call to ParamsMgr::search failed: $errmsg"); } # Check to see if user wants to run setup at all if ($actions->{SETUP} == 0) { $Log->comment("Skipping setup due to actions flag set to \"0\""); return $NACL::AppBuilder::EnGenX::TRUE; } # Verify resources have been loaded. $self->_verify_resources_loaded(); $self->{sm_controller_object}->setup( run_id_prefix => $self->{run_id_prefix}, resource_list => \%{$self->{resource_list}} ); $self->_config(%args); return $NACL::AppBuilder::EnGenX::TRUE; } ## end sub setup =head2 cleanup Cleanup each resource type (controllers, clients, switches, etc.) $SetupCleanup_obj->cleanup(); =over =item Options TBD =over =item C<< TBD >> TBD =back =item Return TBD =item Exceptions BaseException =cut ############################################################################### sub cleanup { my ($self) = @_; &SubIPC::remove_subtest(); my ($st_status, $subtest); # Get the set of actions parameters from ParamsMgr my $actions = &ParamsMgr::search(search_path => [], key => "ACTIONS"); if (&ParamsMgr::error()) { $self->_set_err(&ParamsMgr::get_error()); return $NACL::AppBuilder::EnGenX::FALSE; } # Check to see if user wants to run cleanup at all if ($actions->{CLEANUP} == 0) { $Log->comment("Skipping cleanup due to actions flag set to \"0\""); return $NACL::AppBuilder::EnGenX::TRUE; } # Verify resources have been loaded. $self->_verify_resources_loaded(); my $start; my $difference; try { $EnGenXUtil::OBJ_COUNT = 0; $start = [Time::HiRes::gettimeofday()]; ############################################## # Controller cleanup ############################################## $self->{sm_controller_object}->cleanup( run_id_prefix => $self->{run_id_prefix}, resource_list => \%{$self->{resource_list}} ); } finally { if ($EnGenXUtil::OBJ_COUNT) { $difference = Time::HiRes::tv_interval($start); $self->_log_performance( time_difference => $difference, action => 'cleanup' ); } }; $self->_deconfig(); return $NACL::AppBuilder::EnGenX::TRUE; } ## end sub cleanup sub _deconfig { my ($self) = @_; # Get the set of actions parameters from ParamsMgr my $actions = &ParamsMgr::search(search_path => [], key => "ACTIONS"); if (&ParamsMgr::error()) { $self->_set_err(&ParamsMgr::get_error()); return $NACL::AppBuilder::EnGenX::FALSE; } # Check to see if user wants to run deconfig at all if ($actions->{DECONFIG} == 0) { $Log->comment("Skipping deconfig due to actions flag set to \"0\""); return TRUE; } # Verify resources have been loaded. $self->_verify_resources_loaded(); ############################################## # Cluster deconfig ############################################## $self->{sm_controller_object}->_deconfig( run_id_prefix => $self->{run_id_prefix}, resource_list => \%{$self->{resource_list}} ); return TRUE; } ## end sub _deconfig =head2 validate Validate each resource type (controllers, clients, switches, etc.) $SetupCleanup_obj->validate(); =over =item Options TBD =over =item C<< TBD >> TBD =back =item Return TBD =item Exceptions BaseException =cut ############################################################################### sub validate { my ($self) = @_; my ($st_status, $subtest); # Get the set of actions parameters from ParamsMgr my $actions = &ParamsMgr::search(search_path => [], key => "ACTIONS"); if (&ParamsMgr::error()) { my $errmsg = &ParamsMgr::get_error(); NATE::BaseException->throw( "Call to ParamsMgr::search failed: $errmsg"); } # Check to see if user wants to run validation at all if ($actions->{VALIDATE} == 0) { $Log->comment("Skipping validation due to actions flag set to \"0\""); return TRUE; } # Controller validate ############################################## $self->{sm_controller_object}->validate(); return TRUE; } ## end sub validate =head2 load_resources Go through all resources provided by caller (XML file) and create necessary objects for each to be stored on EnGenX for later use $SetupCleanup_obj->load_resources(); =over =item Options TBD =over =item C<< TBD >> TBD =back =item Return TBD =item Exceptions BaseException =cut ############################################################################### sub load_resources { my ($self, %args) = @_; $Log->enter() if $may_enter; &ParamsMgr::process_args(%args); # load the controller modules resources try { $self->{sm_controller_object}->_load_resource(); } otherwise { my $e = shift; $self->_set_err( "Exception caught during controller load resource call: " . $e->text()); }; # finally, if no error was encountered, mark LOAD_RESOURCES as executed $self->{resources_loaded} = TRUE; $Log->exit() if $may_exit; return TRUE; } ## end sub load_resources =head2 load_xml Load XML file(s) into ParamSet via ParamsMgr $SetupCleanup_obj->load_xml(); =over =item Options TBD =over =item C<< TBD >> TBD =back =item Return TBD =item Exceptions BaseException =cut ############################################################################### sub load_xml { $Log->enter() if $may_enter; my ($self, %args) = @_; &ParamsMgr::process_args(%args); my @xml_files = &ParamsMgr::get_arg( -arg => "xml_files", -required => 1, -ref_type => "ARRAY" ); my @override_schema_files = &ParamsMgr::get_arg( -arg => "schema_files", -ref_type => "ARRAY", -default => [] ); my $display_xml = &ParamsMgr::get_arg( -arg => "display_xml", -default => 1 ); my $runid = &ParamsMgr::get_arg( -arg => "display_xml_runid", -default => "XML-environment" ); if (&ParamsMgr::error()) { $Log->exit() if $may_exit; my $errmsg = join "\n", &ParamsMgr::get_error(); NATE::BaseException->throw("Argument parsing failed: $errmsg"); } # Use the EnGenX internal schema files for each module # if requested to (default) my @schema_files = @DTD_SCHEMA_FILES; # if any override schema files were specified, push them on the list if (scalar(@override_schema_files)) { push(@schema_files, @override_schema_files); } # validate & load each xml_file into ParamsMgr foreach my $xml_file (@xml_files) { # Validate the XML file against the provided schema file list my $xml_string = &ParamsMgr::validate_and_process_xml( -schema_files => \@schema_files, -xml_file => $xml_file ); if (&ParamsMgr::error()) { $Log->exit() if $may_exit; my $errmsg = join "\n", &ParamsMgr::get_error(); NATE::BaseException->throw( "Failed to validate XML file: " . "$xml_file: $errmsg"); } # Load the xml string that was generated by validate_xml into paramsmgr &ParamsMgr::load_xml( -xml_string => $xml_string, -display_xml => $display_xml, -display_xml_runid => $runid ); if (&ParamsMgr::error()) { $Log->comment( "Failed to load XML String" . &ParamsMgr::get_error()); $Log->exit() if $may_exit; my $errmsg = join "\n", &ParamsMgr::get_error(); NATE::BaseException->throw("Failed to load XML string: $errmsg"); } } $Log->exit() if $may_exit; return TRUE; } ## end sub load_xml =head2 xml_to_list Translate the XML stored by ParamsMgr into a ResourceList $SetupCleanup_obj->xml_to_list(); =over =item Options None =back =item Return Nothing, resource_list attribure is populated =item Exceptions BaseException =cut ############################################################################### sub xml_to_list { my ($self) = @_; $Log->enter() if $may_enter; $Log->comment("Translating XML to Resource List"); # Get the actions for the environment and save them (may have override) my $actions = &ParamsMgr::search(search_path => [], key => "ACTIONS"); if (&ParamsMgr::error()) { $Log->exit() if $may_exit; my $errmsg = &ParamsMgr::errmsg(); NATE::BaseException->throw("Failed to find ACTIONS param: $errmsg"); } # Set action variables. The defaults are for completeness sake, the # defaults set in the DTD file should be in effect before we get here my $action_setup = ($actions->{SETUP}) ? $actions->{SETUP} : 0; my $action_cleanup = ($actions->{CLEANUP}) ? $actions->{CLEANUP} : 0; my $action_config = ($actions->{CONFIG}) ? $actions->{CONFIG} : 0; my $action_deconfig = ($actions->{DECONFIG}) ? $actions->{DECONFIG} : 0; my $action_validate = ($actions->{VALIDATE}) ? $actions->{VALIDATE} : 0; my $action_precleanup = ($actions->{PRECLEANUP}) ? $actions->{PRECLEANUP} : 0; my $cmd_interface = "zapi"; # Get the policies for the environment and save them (may have override) my $policies = &ParamsMgr::search(search_path => [], key => "REUSE_POLICY"); if (&ParamsMgr::error()) { $Log->exit() if $may_exit; my $errmsg = &ParamsMgr::errmsg(); NATE::BaseException->throw( "Failed to find REUSE_POLICY param: $errmsg"); } my $reuse_policy = ($policies->{SETUP}) ? $policies->{SETUP} : "reuse"; my $cleanup_policy = ($policies->{CLEANUP}) ? $policies->{CLEANUP} : 0; # Get the environment global options $self->_retrieve_iscsi_global_options(); # Get the information in the CLUSTERS tag. This should be any CLUSTER # tags, setup, config, deconfig, etc. my $clusters = &ParamsMgr::search( -search_path => [], -key => "CLUSTERS" ); if (&ParamsMgr::error()) { $Log->exit() if $may_exit; my $errmsg = &ParamsMgr::errmsg(); NATE::BaseException->throw("Failed to find CLUSTERS param: $errmsg"); } # We default these to what is set in the actions and reuse_policy tags. # Then we check to see if the clusters tag has overrides. As we process # each cluster, we check them there as well. my $data_hash = { _cleanup_policy => $cleanup_policy, _reuse_policy => $reuse_policy, _parallel => 0, _interface => $cmd_interface, _load_balance => 0, _setup => $action_setup, _cleanup => $action_cleanup, _config => $action_config, _deconfig => $action_deconfig, _validate => $action_validate, _precleanup => $action_precleanup, }; $data_hash = $self->_get_current_action( cluster_hash => $clusters, data_hash => $data_hash, label => "CLUSTERS" ); # We are going to store the clusters attributes in the ResourceList. # Look at each key, skip the hashes and store the data. my $clusters_attributes = {}; while (my ($clusters_key, $clusters_value) = each(%$clusters)) { next if (ref($clusters_value)); $clusters_attributes->{$clusters_key} = $clusters_value; } $self->{resource_list}->add_resource( category => "CLUSTERS", type => "attributes", data => $clusters_attributes, ); # When we walk the XML we send in 3 hash references # - data_hash - EnGenX data # - pkey_hash - Primary Keys (cluster, vserver, etc) # - walk_hash - the current hash to walk (cluster at this level) my $pkey_hash = {}; # Now go through the CLUSTERS keys and process each CLUSTER tag while (my ($clusters_key, $clusters_value) = each(%$clusters)) { next if (ref($clusters_value) ne 'HASH'); $self->_xml_walk( $clusters_key, $clusters_key, $clusters_value, $data_hash, $pkey_hash ); } # end while $Log->exit() if $may_exit; } ## end sub xml_to_list ############################################################################### # Method: _xml_walk # Objective: Walk thorugh the hash from the XML and create Resources # Inputs: top level key, current key, current hash, # data from parent, primary_keys hash # Outputs: Returns nothing, ResourceList populated # ############################################################################### sub _xml_walk() { my ($self, $category, $current_key, $walk_hash, $data_hash, $pkey_hash) = @_; if ($Log->may_debug()) { $Log->debug("Category is $category, Current key is $current_key" . "\nDumper of $current_key walk_hash: " . Dumper($walk_hash)); $Log->debug( "Dumper of $current_key data_hash: " . Dumper($data_hash)); } # If the current key is "VOL" or "AGGR" change to "VOLUME", "AGGREGATE" # Need to account for trailing numbers, since ParamsManager adds them when # it encounters more than one cluster defined in the xml. if ($category =~ /^CLUSTER($|\d*)/i and $current_key =~ /^VOL(\d*)/i) { $current_key = "VOLUME$1"; } if ($category =~ /^CLUSTER($|\d*)/i and $current_key =~ /^AGGR(\d*)/i) { $current_key = "AGGREGATE$1"; } # Set the EnGenX data based on either the data_hash or the walk hash # or a default. The defaults match what's in our DTD files. # The values for current_cluster and current_vserver are set # after we generate copies because they depend on the output from # generate_copies(). # # Policies my $current_cleanup_policy = $self->_get_parent_value("CLEANUP_POLICY", "_cleanup_policy", $walk_hash, $data_hash, "new"); my $current_reuse = $self->_get_parent_value("REUSE", "_reuse_policy", $walk_hash, $data_hash, "reuse"); my $current_parallel = $self->_get_parent_value("PARALLEL", "_parallel", $walk_hash, $data_hash, 0); my $current_cmd_interface = $self->_get_parent_value("CMD_INTERFACE", "_interface", $walk_hash, $data_hash, "zapi"); my $current_load_balance = $self->_get_parent_value("LOAD_BALANCE_PROVISIONING", "_load_balance", $walk_hash, $data_hash, 0); # Actions my $current_setup = $self->_get_parent_value("SETUP", "_setup", $walk_hash, $data_hash, 1); my $current_cleanup = $self->_get_parent_value("CLEANUP", "_cleanup", $walk_hash, $data_hash, 1); my $current_config = $self->_get_parent_value("CONFIG", "_config", $walk_hash, $data_hash, 1); my $current_deconfig = $self->_get_parent_value("DECONFIG", "_deconfig", $walk_hash, $data_hash, 1); my $current_validate = $self->_get_parent_value("VALIDATE", "_validate", $walk_hash, $data_hash, 0); my $current_precleanup = $self->_get_parent_value("PRECLEANUP", "_precleanup", $walk_hash, $data_hash, 0); # Grab a host typ to be used for the add resource call my $hash_type = $current_key; $hash_type =~ s/(\D*)(\d+)/$1/; # remove any trailing digits LUN2->LUN # Use generate_copies to create multiple XML entries for resources # with a count > 0 my @element_array = (); push( @element_array, $self->_generate_copies( element => $walk_hash, 'type' => $hash_type, ) ); # Now that we have an array of copies (can be just one), we # process each one (depth first recursion, etc foreach my $element (@element_array) { # Make a copy of the pkey_hash to send to the next level # since we are passing around referecnes to hashes, if we # don't create a clone, we could affect this level when we edit # it at the next level. my $primary = Clone::clone($pkey_hash); # If element contains a NAME field, add it to the primary key hash if (defined $walk_hash->{NAME}) { my $name = $element->{NAME}; my $pkey = $current_key; # Strip the digits off the end of the key if ($pkey =~ /(\D*)(\d+)/) { $pkey = $1; } $pkey = '_CLUSTER' if ($pkey eq 'CLUSTER'); $primary->{$pkey} = $name; $primary->{_protocol} = $walk_hash->{PROTOCOL} if ($pkey eq 'IGROUP'); } # Process each key in the elements hash. Skip scalars and for each # HASH that we encounter, create the data hash needed by this method # and recursively call _xml_walk() to process the key. while (my ($key, $value) = each(%$element)) { next if (ref($value) ne 'HASH'); my $element_data = { _cleanup_policy => $current_cleanup_policy, _reuse_policy => $current_reuse, _parallel => $current_parallel, _interface => $current_cmd_interface, _load_balance => $current_load_balance, _setup => $current_setup, _cleanup => $current_cleanup, _config => $current_config, _deconfig => $current_deconfig, _validate => $current_validate, _precleanup => $current_precleanup, }; # We clone $primary, so that siblings can not modify each others primary keys. $self->_xml_walk($category, $key, $value, $element_data, Clone::clone($primary)); } # end while # When we get here we have gone through all of our curent hash keys # and have drilled down into any children. We now get our push # data together and push/add ourselves as a resource. # Go through the hash pull out the data that we will store my $push_data = {}; while (my ($data_key, $data_value) = each(%$element)) { # Skip anything that's not a scalar if (ref($data_value)) { if ($hash_type =~ /LIF/i) { # IP_RANGE has been transformed to an ARRAY REF that needs # to be added to it's corresponding resource obj. if ($data_key =~ /ips/i) { $push_data->{$data_key} = $data_value; } # DATA-PROTOCOL should be an array ref, so we must assigne # it to the corresponding key elsif ($data_key =~ /DATA-PROTOCOL/i) { $push_data->{$data_key} = $data_value; } } elsif ($hash_type =~ /^PORTSET\d*$/i) { if ($data_key =~ /^igroups$/i) { $push_data->{$data_key} = $data_value; } } elsif ($hash_type =~ /^VOLUME$/i) { if ($data_key =~ /^data-aggr-list$/i) { $push_data->{$data_key} = $data_value; } } elsif ($hash_type =~ /^VSERVER$/i) { if ($data_key =~ /^aggr-list$/i) { $push_data->{$data_key} = $data_value; } } next; } # Skip the stuff stored in the $current_ variables, added later next if ($data_key =~ /^(CLEANUP_POLICY|REUSE|PARALLEL|CMD_INTERFACE|LOAD_BALANCE_PROVISIONING|SETUP|CLEANUP|CONFIG|DECONFIG|VALIDATE|PRECLEANUP)$/ ); if ($data_key eq "AGGR") { $data_key = "AGGREGATE"; } $push_data->{$data_key} = $data_value; } # Add the primary keys to the data to be stored while (my ($data_key, $data_value) = each(%$pkey_hash)) { $push_data->{$data_key} = $data_value; # The ips_key will be used to search for the next available IP # Address when creating the lif. It needs to be unique, so # concatenate the vserver and the lif name. if (($hash_type =~ /LIF/i) && ($data_key =~ /VSERVER/i)) { if (exists($push_data->{ips_key})) { $push_data->{ips_key} = $data_value . '_' . $push_data->{ips_key}; } } } if ($current_reuse =~ /check|fail/i) { $current_reuse = "die"; } # Replace the EnGenX fields in the element data with # what we determined the current values were. $push_data->{_cleanup_policy} = $current_cleanup_policy; $push_data->{if_exists} = $current_reuse; $push_data->{_parallel} = $current_parallel; $push_data->{_interface} = $current_cmd_interface; $push_data->{_load_balance} = $current_load_balance; $push_data->{_setup} = $current_setup, $push_data->{_cleanup} = $current_cleanup, $push_data->{_config} = $current_config, $push_data->{_deconfig} = $current_deconfig, $push_data->{_validate} = $current_validate, $push_data->{_precleanup} = $current_precleanup, $push_data->{_validate_done} = 0; $push_data->{_config_done} = 0; $push_data->{_setup_done} = 0; $push_data->{_deconfig_done} = 0; $push_data->{_cleanup_done} = 0; $push_data->{_precleanup_done} = 0; # When you add more than one cluster or multiple clients, you'll need # the category to be CLUSTER_ or CLIENT_ my $cat_to_add = $category; # Remove any trailing numbers $cat_to_add =~ s/(\D*)(\d+)/$1/; # Assign the catergory either as a key or an attribute if ($hash_type =~ /^CLUSTER$/) { $cat_to_add .= "_" . $walk_hash->{NAME}; } else { $cat_to_add .= "_" . $push_data->{_CLUSTER}; } $push_data->{_parallel} = 5 if ($push_data->{_parallel} =~ /max/i); # Generate special case resources before adding the resource so that # if there are special cases then the special case fields can be # removed before adding the resource. $self->_generate_special_case_resources($cat_to_add, $hash_type, $push_data, $pkey_hash); foreach my $key (keys(%$push_data)) { $push_data->{lc($key)} = delete $push_data->{$key}; } if ($hash_type =~ /^cluster_peer/i) { $push_data->{cluster} = $push_data->{_cluster}; } $self->{resource_list}->add_resource( category => $cat_to_add, type => lc($hash_type), data => $push_data ); } # end foreach element if (defined $self->{rgname}) { my $cbdata = $self->{resource_list} ->get_resources(category => "^CLUSTER", type => lc($hash_type)); $Log->debug("Adding type $hash_type to configbroker\n"); $Log->debug(Dumper($cbdata)); $self->{rgh}->add_resources( resource_type => lc($hash_type), resourcearrayref => $cbdata ) if (defined $self->{rgname}); } if ($Log->may_debug()) { $Log->debug("Out Dumper of $current_key walk_hash: " . Dumper($walk_hash) . "\nOut Dumper of $current_key data_hash: " . Dumper($data_hash) . "\nOut Dumper of resource list: " . Dumper($self->{resource_list})); } } ## end sub _xml_walk() ############################################################################### # Method: _generate_special_case_resources # Objective: Calls methods to take care of special resource cases. # Inputs: $resource_list - reference to a Resource List # $category - the category of the resource to generate special cases # $type - the type of the resource containing the map list # $resource_data - the data for the resource to generate special cases # $resource_pkey_hash - the primary key hash for the resource # to generate special cases # Returns: none ############################################################################### sub _generate_special_case_resources() { my ($self, $category, $type, $resource_data, $resource_pkey_hash) = @_; if ($type =~ /^LUN$/i) { if (exists $resource_data->{MAP_LIST}) { $self->_expand_map_list_to_resources($category, $type, $resource_data, $resource_pkey_hash); } if (exists $resource_data->{BIND_LIST}) { $self->_expand_bind_list_to_resources($category, $type, $resource_data, $resource_pkey_hash); } if (exists $resource_data->{CREATE_FROM_FILE}) { $self->_expand_create_from_file_to_resources($category, $type, $resource_data, $resource_pkey_hash); } # Policy groups cannot start with _ so _default # is the indicator that this should be # changed to the primary keys generated name. if (exists $resource_data->{'QOS-POLICY-GROUP'} and $resource_data->{'QOS-POLICY-GROUP'} =~ /^_default/i) { $self->_build_policy_group_name($category, $type, $resource_data, $resource_pkey_hash, FALSE); } } elsif ($type =~ /^IGROUP$/i and exists $resource_data->{INITIATOR_LIST}) { $self->_expand_initiator_list_to_resources($category, $type, $resource_data, $resource_pkey_hash); } elsif ($type =~ /^FAILOVER_GROUP$/i) { $self->_add_failover_group_resources_for_extra_ports($category, $type, $resource_data, $resource_pkey_hash); } elsif ($type =~ /^VSERVER$/i) { #if ($resource_data->{ISCSI_ENABLED} == 1) { # $self->_expand_iscsi_security_settings_to_resources($category, # $type, $resource_data, $resource_pkey_hash); #} # Policy groups cannot start with _ so _default # is the indicator that this should be # changed to the primary keys generated name. if (exists $resource_data->{'QOS-POLICY-GROUP'} and $resource_data->{'QOS-POLICY-GROUP'} =~ /^_default/i) { $self->_build_policy_group_name($category, $type, $resource_data, $resource_pkey_hash, FALSE); } } elsif ($type =~ /^VOLUME$/i) { # Policy groups cannot start with _ so _default # is the indicator that this should be # changed to the primary keys generated name. if (exists $resource_data->{'QOS-POLICY-GROUP'} and $resource_data->{'QOS-POLICY-GROUP'} =~ /^_default/i) { $self->_build_policy_group_name($category, $type, $resource_data, $resource_pkey_hash, FALSE); } # Policy groups cannot start with _ so _default # is the indicator that this should be # changed to the primary keys generated name. } elsif ($type =~ /^QOS_POLICY_GROUP$/i and $resource_data->{NAME} =~ /^_default/i) { $self->_build_policy_group_name($category, $type, $resource_data, $resource_pkey_hash, TRUE); } return; } ## end sub _generate_special_case_resources() #============================================================================== # Method: _build_policy_group_name # Objective: Build the policy group name from args. # Details: Build policy-group name from vserver,vol,qtree,lun. # Inputs: %args from create or delete # Outputs: The created name # Exceptions: Node #============================================================================== sub _build_policy_group_name { my ($self, $category, $type, $resource_data, $resource_pkey_hash, $is_qos_policy_group) = @_; $Log->enter() if $may_enter; my $policy_name = ""; if ($is_qos_policy_group) { $policy_name = delete $resource_data->{'NAME'} if exists($resource_data->{'NAME'}); $policy_name = delete $resource_data->{'POLICY-GROUP'} if exists($resource_data->{'POLICY-GROUP'}); } else { $policy_name = delete $resource_data->{'QOS-POLICY-GROUP'} if exists($resource_data->{'QOS-POLICY-GROUP'}); } $policy_name =~ s/_default//i; $policy_name = "pg" . $policy_name; $policy_name = $resource_data->{NAME} . "_" . $policy_name unless ($is_qos_policy_group); $policy_name = $resource_pkey_hash->{LUN} . "_" . $policy_name if (exists($resource_pkey_hash->{LUN}) and length($resource_pkey_hash->{LUN})); $policy_name = $resource_pkey_hash->{QTREE} . "_" . $policy_name if (exists($resource_pkey_hash->{QTREE}) and length($resource_pkey_hash->{QTREE})); $policy_name = $resource_pkey_hash->{VOLUME} . "_" . $policy_name if (exists($resource_pkey_hash->{VOLUME}) and length($resource_pkey_hash->{VOLUME})); $policy_name = $resource_pkey_hash->{VSERVER} . "_" . $policy_name if (exists($resource_pkey_hash->{VSERVER}) and length($resource_pkey_hash->{VSERVER})); if ($is_qos_policy_group) { $resource_data->{'POLICY-GROUP'} = $policy_name; } else { $resource_data->{'QOS-POLICY-GROUP'} = $policy_name; } $Log->exit() if $may_exit; return; } ## end sub _build_policy_group_name ############################################################################### # Method: add_failover_group_resources_for_extra_ports # Objective: Expands the map_list parameter on Luns to individual lun_map # resources # Inputs: $resource_list - reference to a Resource List # $category - the category of the resource containing the map list # $type - the type of the resource containing the map list # $resource_data - the data for the resource containing the map list # $resource_pkey_hash - the primary key hash for the resource # containing the map list # Returns: none ############################################################################### sub _add_failover_group_resources_for_extra_ports() { my ($self, $category, $type, $resource_data, $resource_pkey_hash) = @_; $Log->enter() if $may_enter; my $failover_group_data = {}; # Replace the EnGenX fields in the element data with # what we determined the current values were. $failover_group_data->{_cleanup_policy} = $resource_data->{_cleanup_policy}; $failover_group_data->{if_exists} = $resource_data->{if_exists}; $failover_group_data->{_parallel} = $resource_data->{_parallel}; $failover_group_data->{_interface} = $resource_data->{_interface}; $failover_group_data->{_load_balance} = $resource_data->{_load_balance}; $failover_group_data->{_setup} = $resource_data->{_setup}; $failover_group_data->{_cleanup} = $resource_data->{_cleanup}; $failover_group_data->{_config} = $resource_data->{_config}; $failover_group_data->{_deconfig} = $resource_data->{_deconfig}; $failover_group_data->{_validate} = $resource_data->{_validate}; $failover_group_data->{_precleanup} = $resource_data->{_precleanup}; $failover_group_data->{_validate_done} = 0; $failover_group_data->{_config_done} = 0; $failover_group_data->{_setup_done} = 0; $failover_group_data->{_deconfig_done} = 0; $failover_group_data->{_cleanup_done} = 0; $failover_group_data->{_precleanup_done} = 0; # Add the primary keys from lun to the data to be stored while (my ($data_key, $data_value) = each(%$resource_pkey_hash)) { $failover_group_data->{lc($data_key)} = $data_value; } # Add the name key from the lun resource because it is a pkey of # the failover_group $failover_group_data->{'failover-group'} = $resource_data->{NAME}; # Add the other required data that is not ports. $failover_group_data->{'node'} = $resource_data->{NODE}; # store the ports that are part of this failover group in an array my $ports_array = [split(/\s*,\s*/, $resource_data->{PORTS})]; # remove ports since you dont need it. $resource_data->{PORT} = pop @$ports_array; delete $resource_data->{PORTS}; while (scalar @$ports_array) { $failover_group_data->{port} = pop @$ports_array; # add the failover group resource $self->{resource_list}->add_resource( category => $category, type => 'failover_group', data => Clone::clone($failover_group_data) ); } $Log->exit() if $may_exit; return; } ## end sub _add_failover_group_resources_for_extra_ports() ############################################################################### # Method: expand_map_list_to_resources # Objective: Expands the map_list parameter on Luns to individual lun_map # resources # Inputs: $resource_list - reference to a Resource List # $category - the category of the resource containing the map list # $type - the type of the resource containing the map list # $resource_data - the data for the resource containing the map list # $resource_pkey_hash - the primary key hash for the resource # containing the map list # Returns: none ############################################################################### sub _expand_map_list_to_resources() { my ($self, $category, $type, $resource_data, $resource_pkey_hash) = @_; $Log->enter() if $may_enter; my $lun_map_data = {}; # Replace the EnGenX fields in the element data with # what we determined the current values were. $lun_map_data->{_cleanup_policy} = $resource_data->{_cleanup_policy}; $lun_map_data->{if_exists} = $resource_data->{if_exists}; $lun_map_data->{_parallel} = $resource_data->{_parallel}; $lun_map_data->{_interface} = $resource_data->{_interface}; $lun_map_data->{_load_balance} = $resource_data->{_load_balance}; $lun_map_data->{_setup} = $resource_data->{_setup}; $lun_map_data->{_cleanup} = $resource_data->{_cleanup}; $lun_map_data->{_config} = $resource_data->{_config}; $lun_map_data->{_deconfig} = $resource_data->{_deconfig}; $lun_map_data->{_validate} = $resource_data->{_validate}; $lun_map_data->{_precleanup} = $resource_data->{_precleanup}; $lun_map_data->{_validate_done} = 0; $lun_map_data->{_config_done} = 0; $lun_map_data->{_setup_done} = 0; $lun_map_data->{_deconfig_done} = 0; $lun_map_data->{_cleanup_done} = 0; $lun_map_data->{_precleanup_done} = 0; # Add the primary keys from lun to the data to be stored while (my ($data_key, $data_value) = each(%$resource_pkey_hash)) { $lun_map_data->{lc($data_key)} = $data_value; } # Add the name key from the lun resource because it is a pkey of # the lun_map $lun_map_data->{lun} = $resource_data->{NAME}; # store the igroups that are part of this lun map on the igroups array $lun_map_data->{igroups} = [split(/\s*,\s*/, $resource_data->{MAP_LIST})]; # store a few extra fields required for explicit lun id mappings $lun_map_data->{require_same_lun_id_per_igroup} = $resource_data->{REQUIRE_SAME_LUN_ID_PER_IGROUP}; $lun_map_data->{'lun-id'} = $resource_data->{'LUN-ID'}; $lun_map_data->{lun_id_range} = $resource_data->{LUN_ID_RANGE}; # add the lun map resource $self->{resource_list}->add_resource( category => $category, type => 'lun_map', data => Clone::clone($lun_map_data) ); $Log->exit() if $may_exit; return; } ## end sub _expand_map_list_to_resources() ############################################################################### # Method: expand_bind_list_to_resources # Objective: Expands the bind_list parameter on Luns to individual lun_bind # resources # Inputs: $resource_list - reference to a Resource List # $category - the category of the resource containing the bind list # $type - the type of the resource containing the bind list # $resource_data - the data for the resource containing the bind list # $resource_pkey_hash - the primary key hash for the resource # containing the bind list # Returns: none ############################################################################### sub _expand_bind_list_to_resources() { my ($self, $category, $type, $resource_data, $resource_pkey_hash) = @_; $Log->enter() if $may_enter; my $lun_bind_data = {}; # Replace the EnGenX fields in the element data with # what we determined the current values were. $lun_bind_data->{_cleanup_policy} = $resource_data->{_cleanup_policy}; $lun_bind_data->{if_exists} = $resource_data->{if_exists}; $lun_bind_data->{_parallel} = $resource_data->{_parallel}; $lun_bind_data->{_interface} = $resource_data->{_interface}; $lun_bind_data->{_load_balance} = $resource_data->{_load_balance}; $lun_bind_data->{_setup} = $resource_data->{_setup}; $lun_bind_data->{_cleanup} = $resource_data->{_cleanup}; $lun_bind_data->{_config} = $resource_data->{_config}; $lun_bind_data->{_deconfig} = $resource_data->{_deconfig}; $lun_bind_data->{_validate} = $resource_data->{_validate}; $lun_bind_data->{_precleanup} = $resource_data->{_precleanup}; $lun_bind_data->{_validate_done} = 0; $lun_bind_data->{_config_done} = 0; $lun_bind_data->{_setup_done} = 0; $lun_bind_data->{_deconfig_done} = 0; $lun_bind_data->{_cleanup_done} = 0; $lun_bind_data->{_precleanup_done} = 0; # Add the primary keys from lun to the data to be stored while (my ($data_key, $data_value) = each(%$resource_pkey_hash)) { $lun_bind_data->{lc($data_key)} = $data_value; } # Make the VVOL PATH from the lun resource because it is a pkey of # the lun_bind my $volume = $lun_bind_data->{volume}; my $qtree = $lun_bind_data->{qtree}; my $lun = $resource_data->{NAME}; if (defined($qtree) and length($qtree)) { $lun_bind_data->{'vvol-path'} = "/vol/" . $volume . "/" . $qtree . "/" . $lun; } else { $lun_bind_data->{'vvol-path'} = "/vol/" . $volume . "/" . $lun; } # Delete volume and qtree because they are not part of the lun_bind # args and are contained in the vvol path delete $lun_bind_data->{volume}; delete $lun_bind_data->{qtree}; # create a resource obj for all the bind list entries foreach my $pep (split(/,/, delete $resource_data->{BIND_LIST})) { # the protocol endpoint path may come in different styles, # the purpose of this logic is to convert all the possible # scenarios into an absolute lun path # Possible values of $pep: # (1) Absolute path # e.g: /vol/myvol/mypep # e.g: /vol/myvol/myqtree/mypep # (2) Relative path of a volume (vol: /vol/myvol) # e.g: ./mypep --> /vol/myvol/mypep # e.g: mypep --> /vol/myvol/mypep # e.g: myqtree/mypep --> /vol/myvol/myqtree/mypep # (3) Relative path of a qtree inside a vol (vol: /vol/myvol/myqtree) # e.g: ./mypep --> /vol/myvol/myqtree/mypep # e.g: mypep --> /vol/myvol/myqtree/mypep # e.g: ../mypep --> /vol/myvol/mypep # e.g: ../myqtree2/mypep --> /vol/myvol/myqtree2/mypep # get rid of spaces $pep =~ s/\s+//g; # get rid of ./ in the bind list param if the user specified it $pep =~ s/^\.\///; if ($pep !~ /^\/vol/) { # pep is a relative path to a lun # add the vol name to the path # check if it has a qtree in it if (not defined($qtree) or not length($qtree)) { # the current vvol lun is not in a qtree # this handles implcict and explicit relative qtree pep cases $pep = "/vol/" . $volume . "/" . $pep; } else { # the current vvol is in a qtree if ($pep =~ /^\.\.\//) { # remove the ../ from the pep $pep =~ s/^\.\.\///; # the user does not want this pep to be in the qtree, prepend just the vol name $pep = "/vol/" . $volume . "/" . $pep; } else { # this pep is a relative path with an implicit qtree name, # ie: it does not contain a qtree name, but the vvol is in a qtree # prepend the current vol name with the qtree $pep = "/vol/" . $volume . "/" . $qtree . "/" . $pep; } } } $lun_bind_data->{'protocol-endpoint-path'} = $pep; $self->{resource_list}->add_resource( category => $category, type => 'lun_bind', data => Clone::clone($lun_bind_data) ); } $Log->exit() if $may_exit; return; } ## end sub _expand_bind_list_to_resources() ############################################################################### # Method: expand_create_from_file_to_resources # Objective: Expands the create_from_file parameter on Luns to individual lun_bind # resources # Inputs: $resource_list - reference to a Resource List # $category - the category of the resource containing create_from_file # $type - the type of the resource containing create_from_file # $resource_data - the data for the resource containing create_from_file # $resource_pkey_hash - the primary key hash for the resource # containing create_from_file # Returns: none ############################################################################### sub _expand_create_from_file_to_resources() { my ($self, $category, $type, $resource_data, $resource_pkey_hash) = @_; $Log->enter() if $may_enter; my $file_data = {}; # Replace the EnGenX fields in the element data with # what we determined the current values were. $file_data->{_cleanup_policy} = $resource_data->{_cleanup_policy}; $file_data->{if_exists} = $resource_data->{if_exists}; $file_data->{_parallel} = $resource_data->{_parallel}; $file_data->{_interface} = $resource_data->{_interface}; $file_data->{_load_balance} = $resource_data->{_load_balance}; $file_data->{_setup} = $resource_data->{_setup}; $file_data->{_cleanup} = $resource_data->{_cleanup}; $file_data->{_config} = $resource_data->{_config}; $file_data->{_deconfig} = $resource_data->{_deconfig}; $file_data->{_validate} = $resource_data->{_validate}; $file_data->{_precleanup} = $resource_data->{_precleanup}; $file_data->{_validate_done} = 0; $file_data->{_config_done} = 0; $file_data->{_setup_done} = 0; $file_data->{_deconfig_done} = 0; $file_data->{_cleanup_done} = 0; $file_data->{_precleanup_done} = 0; # Add the primary keys from lun to the data to be stored while (my ($data_key, $data_value) = each(%$resource_pkey_hash)) { $file_data->{lc($data_key)} = $data_value; } # Remove create_from_file because it is not needed by the lun resource delete $resource_data->{'CREATE_FROM_FILE'}; # Make the file path from the lun resource because it is a pkey of # the file and is needed by the lun as well. my $volume = $file_data->{volume}; my $lun = $resource_data->{NAME}; if (defined($file_data->{qtree}) and length($file_data->{qtree})) { $file_data->{'file'} = "/vol/$volume/$file_data->{qtree}/file_$lun"; $resource_data->{'file-path'} = "/vol/$volume/$file_data->{qtree}/file_$lun"; } else { $file_data->{'file'} = "/vol/$volume/file_$lun"; $resource_data->{'file-path'} = "/vol/$volume/file_$lun"; } # Delete volume and qtree because they are not part of the file # args and are contained in the file path delete $file_data->{volume}; delete $file_data->{qtree}; # Add the size data to the file and remove from the lun. $file_data->{size} = delete $resource_data->{'SIZE'}; # Now we have all the required data that will not change, # add a file resource to the resource list. $self->{resource_list}->add_resource( category => $category, type => 'file', data => Clone::clone($file_data) ); $Log->exit() if $may_exit; return; } ## end sub _expand_create_from_file_to_resources() ############################################################################### # Method: _expand_initiator_list_to_resources # Objective: Expands the initiator_list parameter on Igroups to individual # initiator resources # Inputs: $resource_list - reference to a Resource List # $category - the category of the resource containing the init list # $type - the type of the resource containing the init list # $resource_data - the data for the resource containing the init list # $resource_pkey_hash - the primary key hash for the resource # containing the init list # Returns: none ############################################################################### sub _expand_initiator_list_to_resources() { my ($self, $category, $type, $resource_data, $resource_pkey_hash) = @_; $Log->enter() if $may_enter; my $initiator_data = {}; # Replace the EnGenX fields in the element data with # what we determined the current values were. $initiator_data->{_cleanup_policy} = $resource_data->{_cleanup_policy}; $initiator_data->{if_exists} = $resource_data->{if_exists}; $initiator_data->{_parallel} = $resource_data->{_parallel}; $initiator_data->{_interface} = $resource_data->{_interface}; $initiator_data->{_load_balance} = $resource_data->{_load_balance}; $initiator_data->{_setup} = $resource_data->{_setup}; $initiator_data->{_cleanup} = $resource_data->{_cleanup}; $initiator_data->{_config} = $resource_data->{_config}; $initiator_data->{_deconfig} = $resource_data->{_deconfig}; $initiator_data->{_validate} = $resource_data->{_validate}; $initiator_data->{_precleanup} = $resource_data->{_precleanup}; $initiator_data->{_validate_done} = 0; $initiator_data->{_config_done} = 0; $initiator_data->{_setup_done} = 0; $initiator_data->{_deconfig_done} = 0; $initiator_data->{_cleanup_done} = 0; $initiator_data->{_precleanup_done} = 0; # Add the primary keys from igroup to the data to be stored while (my ($data_key, $data_value) = each(%$resource_pkey_hash)) { $initiator_data->{$data_key} = $data_value; } # Add the name key from the igroup resource because it is a pkey of # the initiator $initiator_data->{igroup} = $resource_data->{NAME}; $initiator_data->{_protocol} = $resource_data->{PROTOCOL}; # Now we have all the required data that will not change, # split the map_list based on ',' and loop through the igroups # to add a initiator resource to the resource list for each igroup. foreach my $initiator (split(/,/, $resource_data->{INITIATOR_LIST})) { $initiator_data->{name} = $initiator; $self->{resource_list}->add_resource( category => $category, type => 'initiator', data => Clone::clone($initiator_data) ); } $Log->exit() if $may_exit; return; } ## end sub _expand_initiator_list_to_resources() ############################################################################### # Method: _get_parent_value # Objective: For things like cleanup_policy, reuse_policy, etc, we will # use a value if we are given one. If not we will check the # parent data, otherwise we use a default. This is to be used # by _xml_walk() # Inputs: walk_hash_key, data_hash_key, walk_hash, data_hash, default # Returns: value of hash_key (or default) ############################################################################### sub _get_parent_value() { my ($self, $walk_hash_key, $data_hash_key, $walk_hash, $data_hash, $default) = @_; my $current_value = $default; if (defined $walk_hash->{$walk_hash_key}) { # We have our own value of this key, use it $current_value = $walk_hash->{$walk_hash_key}; } elsif (defined $data_hash->{$data_hash_key}) { # We don't have our own value but one of our # acestors had a value, inherit that value $current_value = $data_hash->{$data_hash_key}; } return $current_value; } ## end sub _get_parent_value() ############################################################################### # Method: _get_current_action # Objective: Check the given hash for value and set it to either that # value or the given default. # Inputs: hash, hash_key, default, label ############################################################################### sub _get_current_action() { my ($self, @opts) = @_; $Log->enter() if $may_enter; # Locals my %args = (); # Process arguments. try { %args = Params::Validate::validate( @opts, { cluster_hash => {type => HASHREF, optional => FALSE}, data_hash => {type => HASHREF, optional => FALSE}, label => {type => SCALAR, optional => FALSE} } ); } otherwise { my $xcptn = shift; $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw($xcptn->text()); }; # Gather values my $data_hash = $args{data_hash}; my $label = $args{label}; foreach my $key (keys(%{$data_hash})) { # 1. Get the default value my $default = $data_hash->{$key}; # 2. Transform the KEY to a HASH_KEY # That is get rid of the first underscore # and change it to capital letters. my $hash_key = uc(substr($key, 1)); if ($hash_key =~ /INTERFACE/i) { $hash_key = "CMD_" . $hash_key; } # 3. Get the current_action by making the # corresponding call $data_hash->{$key} = $self->_get_xml_override( hash => $args{cluster_hash}, hash_key => $hash_key, default => $default, label => $label ); } $Log->exit() if $may_exit; return (wantarray()) ? %{$data_hash} : $data_hash; } ## end sub _get_current_action() ############################################################################### # Method: _get_xml_override # Objective: Check the given hash for value and set it to either that # value or the given default. # Inputs: hash, hash_key, default, label ############################################################################### sub _get_xml_override() { my ($self, %args) = @_; $Log->enter() if $may_enter; # Process agrs and validate mandatory params &ParamsMgr::process_args(%args); # Get the hash my $hash = &ParamsMgr::get_arg(-arg => "hash", -required => $NACL::AppBuilder::EnGenX::TRUE); if (&ParamsMgr::error()) { $Log->exit() if $may_exit; my $errmsg = &ParamsMgr::errmsg(); NATE::BaseException->throw("Failed to find hash params: $errmsg"); } # Get the hash_key my $hash_key = &ParamsMgr::get_arg(-arg => "hash_key", -required => $NACL::AppBuilder::EnGenX::TRUE); if (&ParamsMgr::error()) { $Log->exit() if $may_exit; my $errmsg = &ParamsMgr::errmsg(); NATE::BaseException->throw("Failed to find hash_key params: $errmsg"); } # Get the default my $default = &ParamsMgr::get_arg(-arg => "default", -required => $NACL::AppBuilder::EnGenX::TRUE); if (&ParamsMgr::error()) { $Log->exit() if $may_exit; my $errmsg = &ParamsMgr::errmsg(); NATE::BaseException->throw("Failed to find default params: $errmsg"); } # Get the label my $label = &ParamsMgr::get_arg(-arg => "label", -required => $NACL::AppBuilder::EnGenX::TRUE); if (&ParamsMgr::error()) { $Log->exit() if $may_exit; my $errmsg = &ParamsMgr::errmsg(); NATE::BaseException->throw("Failed to find label params: $errmsg"); } my $current_value = $default; if (defined($hash->{$hash_key})) { $current_value = $hash->{$hash_key}; } $Log->exit() if $may_exit; return $current_value; } ## end sub _get_xml_override() ############################################################################### # Method: _generate_copies # Objective: Generates "n" copies of an object defined in the params. It's # responsible for renaming and appending indexes to the names # if needed. It also assigns IPs to copies of iSCSI lifs # Input: A reference to a hash containing object attributes # Returns: An array or a reference to an array containing all hashes # with all the attributes corresponding to a certain object. ############################################################################### sub _generate_copies { my ($self, %args) = @_; # Local variables my @objs = (); my @ips_range = (); my $num_copies = 1; # By default create 1 object. my $range = 1; my $offset = 0; # start counting at 0 # Process agrs and validate mandatory params &ParamsMgr::process_args(%args); my $element = &ParamsMgr::get_arg( -arg => "element", -required => $NACL::AppBuilder::EnGenX::TRUE ); my $parent_name = &ParamsMgr::get_arg( -arg => "parent_name", -required => $NACL::AppBuilder::EnGenX::FALSE, -default => "" ); my $type = &ParamsMgr::get_arg( -arg => "type", -required => $NACL::AppBuilder::EnGenX::FALSE, -default => "none" ); # Make sure we don't mess up with the original data structure. my $data = Clone::clone($element); # Check for count or loop if (defined($data->{COUNT})) { $num_copies = delete $data->{COUNT}; } elsif (defined($data->{LOOP})) { $num_copies = delete $data->{LOOP}; } # Check for an index or offset. i.e. an offset of # 50 results in ob_50, ob_51 instead of ob_0, ob_1, etc. if (defined($data->{INDEX})) { $offset = delete $data->{INDEX}; } elsif (defined($data->{OFFSET})) { $offset = delete $data->{OFFSET}; } # If the type is Vserver,Volume, or Lun take care of the children QOS # Policy Groups. if ( $type =~ /^vserver\d*$/i or $type =~ /^volume\d*$/i or $type =~ /^lun\d*$/i) { # Need to loop through all children qos and check: # 1. If no name attribute add NAME=>_default # 2. If parent use_default_qos_policy_group # If all have name attr then create one # With NAME=>_default # Policy groups cannot start with _ so it # is a good indicator that this should be # changed in lower level libraries to the # primary keys generated name. # 3. Set assign_to_vserver if vserver and correct # qos resource my $found_no_name = FALSE; # This is the number appended to the qos hash key my $qos_hash_number = 0; # Name of policy group to assign to this object. my $qos_policy_name = exists $data->{'QOS-POLICY-GROUP'} ? $data->{'QOS-POLICY-GROUP'} : "_NOT_SET"; # Will store the hash key of the qos policy matching # $qos_policy_name if we find one. my $vserver_qos_key = undef; my $vserver_no_name_key = undef; foreach my $key (keys(%{$data})) { if ($key =~ /^(QOS_POLICY_GROUP)/i) { $qos_hash_number = $1 if ($key =~ /(\d+)/); if (not exists($data->{$key}->{NAME})) { $data->{$key}->{NAME} = "_default"; if (exists $data->{$key}->{COUNT} and $data->{$key}->{COUNT} <= 1) { $vserver_no_name_key = $key; $found_no_name = TRUE; } } if ( $type =~ /^vserver/i and $data->{$key}->{NAME} =~ /^$qos_policy_name$/) { # Since this is the qos-policy-group defined to # assigned to the vserver tell the qos resource # this. $data->{$key}->{ASSIGN_TO_VSERVER} = 1; $vserver_qos_key = $key; } } } # Replace/Create the qos-policy-group creation argument if # 'use_default_qos_policy_group' is true(1). if ($data->{USE_DEFAULT_QOS_POLICY_GROUP}) { $data->{'QOS-POLICY-GROUP'} = '_default'; if ($found_no_name) { # Since this is the qos-policy-group defined to # assigned to the vserver tell the qos resource # this. $data->{$vserver_no_name_key}->{ASSIGN_TO_VSERVER} = 1; # use_default_qos_policy_group takes precedence # so remove assign_to_verver if it was added because # qos-policy-group is defined on the vserver. $data->{$vserver_qos_key}->{ASSIGN_TO_VSERVER} = 0 if (defined($vserver_qos_key) and exists $data->{$vserver_qos_key}); } else { my $key = "QOS_POLICY_GROUP$qos_hash_number"; $data->{$key} = {NAME => "_default"}; if ($type =~ /^vserver/i) { # Since this is the qos-policy-group defined to # assigned to the vserver tell the qos resource # this. $data->{$key}->{ASSIGN_TO_VSERVER} = 1; # use_default_qos_policy_group takes precedence # so remove assign_to_verver if it was added because # qos-policy-group is defined on the vserver. $data->{$vserver_qos_key}->{ASSIGN_TO_VSERVER} = 0 if (defined($vserver_qos_key) and exists $data->{$vserver_qos_key}); } } # Remove this because it is not used anywhere else. delete $data->{USE_DEFAULT_QOS_POLICY_GROUP}; # Remove this because vserver assignment is taken care of # in the qos creation. } delete $data->{'QOS-POLICY-GROUP'} if ($type =~ /^vserver/i); } # If there are any attributes at the Cluster tag level, that need to be # updated, set the flag so controller is added to the dispatch table. if ($type =~ /^CLUSTER/i) { foreach my $key (keys(%{$data})) { if ($key =~ /^(AUTOSUPPORT|IPV6)/i) { $self->{modify_clusters}->{$data->{NAME}} = $key; last; } } } # Convert DATA-AGGR-LIST to an array if it exists. elsif ($type =~ /^vol/i) { if (exists($data->{'DATA-AGGR-LIST'})) { my @aggrs = split(/,/, $data->{'DATA-AGGR-LIST'}); $data->{'DATA-AGGR-LIST'} = \@aggrs; } } # If iSCSI or FCP enabled flags are defined, need to create the corresponding # services. And Convert AGGR-LIST to an array if it exists elsif ($type =~ /^vserver/i) { if ( exists($data->{ISCSI_ENABLED}) or exists($data->{FCP_ENABLED}) or exists($data->{NFS_ENABLED}) or exists($data->{CIFS_ENABLED})) { $data = $self->_protocol_services_update($data); } if (exists($data->{'AGGR-LIST'})) { my @aggrs = split(/,/, $data->{'AGGR-LIST'}); $data->{'AGGR-LIST'} = \@aggrs; } } # Collect all IP addresses and lifs elsif ($type =~ /^lif/i) { # Couple of things to do with lifs # # 1. Store the blown out IP addresses for each lif in it's own object. # They will be assigned to each resource accordingly. # 2. Transform the 'data-protocol' input list into an array. foreach my $lif_key (sort keys %{$data}) { # 1 # There could be more than one tag of ADDRESS_RANGEs # so we must verify that the tag name is either plain # ADDRESS_RANGE or ADDRESS_RANGE with a number that # is actually the index of the tag. The regex only # supports 9 address range tags if ($lif_key =~ /^ADDRESS_RANGE($|[1-9]$)/i) { my @ips = $self->_get_ip_addresses($data->{$lif_key}); push(@ips_range, @ips); # Need to delete the Address Range key (tag) to # avoid that the recursive call of _walk_xml generates # an entry for this tag that is not needed. delete $data->{$lif_key}; } # 2 # Transform the 'comma separated list' to an array, and store # the rereference to that array. (as required by NACL) elsif ($lif_key =~ /^DATA-PROTOCOL/i) { my @protocols = split(/,/, $data->{$lif_key}); $data->{$lif_key} = \@protocols; } } } # If PortSet has an IGROUP_BIND tag under it, means that the user wants to # associate (bind) this portset with that specific igroup. elsif ($type =~ /^portset/i) { # Look if the user provided igroups to bind to the portset foreach my $key (keys %{$data}) { if ($key =~ /^IGROUP_BIND\d*$/) { if (exists($data->{$key}->{NAME})) { # store the igroup name in the igroup array push(@{$data->{igroups}}, $data->{$key}->{NAME}); delete($data->{$key}); } } } } # If an IPSPACE has IPSPACE_MEMBER tags under it without DELETE_POLICY defined, # pass down the DELETE_POLICY from IPSPACE to IPSPACE_MEMBER elsif ($type =~ /^ipspace/i) { foreach my $key (keys %{$data}) { if ($key =~ /^IPSPACE_MEMBER\d+$/) { if (not exists($data->{$key}->{DELETE_POLICY})) { $data->{$key}->{DELETE_POLICY} = $data->{DELETE_POLICY}; } } } } # end if # If there's only one copy, leave the name of the resource alone if ($num_copies <= 1) { if ($type =~ /^lif/i) { # Verify there is a range of ips available and # assign it. if (scalar(@ips_range)) { $data->{ips_key} = $data->{NAME}; $data->{ips} = \@ips_range; } } push(@objs, $data); } else { # Go through each element, tweak the name for (my $i = 0; $i < $num_copies; $i++) { my %tmp_copy; # keep the insertion order of the hash so that # everything is kept in the proper order tie(%tmp_copy, "Tie::IxHash"); %tmp_copy = %{$data}; if ( $type =~ /^VOL/i and (($i + $offset) >= 100) and (($i + $offset) < 1000)) { $tmp_copy{NAME} = $data->{NAME} . "_0" . ($i + $offset); } else { $tmp_copy{NAME} = $data->{NAME} . "_" . ($i + $offset); } # TODO: Not sure if the PROTOCOL_COUNT is used, will leave # logic in place to be consistent, but we could delete # the following conde. if ($type =~ /^VSERVER/i and scalar(@ips_range)) { $tmp_copy{PROTOCOL_COUNT} = $i; } # Store a range of IPs for each lif in EnGenX's object. if ($type =~ /^LIF/i) { # If the reference of the var ips has been defined, means # that there was an AddressRange asociated with this LIF # so all the ip_addresses will be assigned to the assigned # to each Lif, The subprocess will take care of distributing # the appropiate ip_address. if (scalar(@ips_range)) { $tmp_copy{ips_key} = $data->{NAME}; $tmp_copy{ips} = \@ips_range; } } push(@objs, \%tmp_copy); } # end for } # end else return (wantarray()) ? @objs : \@objs; } # End _generate_copies ############################################################################### # Method: _get_dtd_path # Objective: Helper method that generates the DTD File path based on # environment variables. # Details: None. Private method. ############################################################################### sub _get_dtd_path { # (1) default to the non null value of SANMGR_DTD_PATH &Tharn::param("ENGENX_DTD_PATH", "-default", ""); my $dtd_path = &ParamsMgr::get("ENGENX_DTD_PATH"); if (defined($dtd_path) and $dtd_path !~ /^\s*$/) { return $dtd_path; } # (2) try to determine from NATE_LIB/NATE_LIB_UNIX my @natelib; if (exists($ENV{"NATE_LIB"})) { @natelib = split(/;/, $ENV{"NATE_LIB"}); } if (not scalar(@natelib) and exists($ENV{"NATE_LIB_UNIX"})) { @natelib = split(/;/, $ENV{"NATE_LIB_UNIX"}); } # walk through all the natelib paths until we find one that works foreach my $natelib_path (@natelib) { $dtd_path = $natelib_path . "/lib/NACL/AppBuiler/SC/DTDFiles"; if (-d $dtd_path) { &ParamsMgr::set(key => "ENGENX_DTD_PATH", value => $dtd_path); return $dtd_path; } } # (3) try to determine the path based on the TESTNAME variable my $testname = $ENV{"TESTNAME"}; if ($testname =~ /^(\/.+?\/p4\/.+?\/)/) { $dtd_path = $1 . "/lib/NACL/AppBuiler/SC/DTDFiles"; # make sure this path exists, otherwise dont use it if (-d $dtd_path) { &ParamsMgr::set(key => "ENGENX_DTD_PATH", value => $dtd_path); return $dtd_path; } } # (4) otherwise, default to sanrtest's version $dtd_path = "/u/sanrtest/p4/test/lib/NACL/AppBuilder/EnGenX/DTDFiles"; &ParamsMgr::set(key => "ENGENX_DTD_PATH", value => $dtd_path); return $dtd_path; } ## end sub _get_dtd_path ############################################################################### # Method: _get_sanmgr_dtd_path # Objective: Helper method that generates the DTD File path based on # environment variables. # Details: None. Private method. ############################################################################### sub _get_sanmgr_dtd_path { # (1) default to the non null value of SANMGR_DTD_PATH &Tharn::param("SANMGR_DTD_PATH", "-default", ""); my $dtd_path = &ParamsMgr::get("SANMGR_DTD_PATH"); if (defined($dtd_path) and $dtd_path !~ /^\s*$/) { return $dtd_path; } # (2) try to determine from NATE_LIB/NATE_LIB_UNIX my @natelib; if (exists($ENV{"NATE_LIB"})) { @natelib = split(/;/, $ENV{"NATE_LIB"}); } if (not scalar(@natelib) and exists($ENV{"NATE_LIB_UNIX"})) { @natelib = split(/;/, $ENV{"NATE_LIB_UNIX"}); } # walk through all the natelib paths until we find one that works foreach my $natelib_path (@natelib) { $dtd_path = $natelib_path . "/lib/San/SanMgr/DTDFiles"; if (-d $dtd_path) { &ParamsMgr::set(key => "SANMGR_DTD_PATH", value => $dtd_path); return $dtd_path; } } # (3) try to determine the path based on the TESTNAME variable my $testname = $ENV{"TESTNAME"}; if ($testname =~ /^(\/.+?\/p4\/.+?\/)/) { $dtd_path = $1 . "lib/San/SanMgr/DTDFiles"; # make sure this path exists, otherwise dont use it if (-d $dtd_path) { &ParamsMgr::set(key => "SANMGR_DTD_PATH", value => $dtd_path); return $dtd_path; } } # (4) otherwise, default to sanrtest's version $dtd_path = "/u/sanrtest/p4/test/lib/San/SanMgr/DTDFiles"; &ParamsMgr::set(key => "SANMGR_DTD_PATH", value => $dtd_path); return $dtd_path; } ## end sub _get_sanmgr_dtd_path ############################################################################### # Method: _get_xml_path # Objective: to get the full path name with extension (xml) # Parameter: $xml_file (Optional) -> user specified xml name # Expected Output if script location is in # "/u/muthug/p4/test/san/lun_mgmt/portset/basic_func.thpl"; # and all xmls exist. # # _get_xml_filename() = # "/u/muthug/p4/test/san/lun_mgmt/portset/basic_func.xml"; # _get_xml_filename("") = # "/u/muthug/p4/test/san/lun_mgmt/portset/basic_func.xml"; # _get_xml_filename("gmk1.xml.xml") = # "/u/muthug/p4/test/san/lun_mgmt/portset/gmk1.xml.xml"; # _get_xml_filename("justfile") = # "/u/muthug/p4/test/san/lun_mgmt/portset/justfile.xml"; # _get_xml_filename("p4/test/san/lun_mgmt/portset/fullpath.xml") = # "p4/test/san/lun_mgmt/portset/fullpath.xml"; # _get_xml_filename("/san/lun_mgmt/portset/fullpathwitoutext") = # "/san/lun_mgmt/portset/fullpathwitoutext.xml"; # _get_xml_filename("/u/any/junk/folder/name/only/portset/") = # "/u/any/junk/folder/name/only/portset/basic_func.xml"; # # Return: returns a string with full path with filename and extension(.xml) ############################################################################### sub _get_xml_path { my ($self, $xml_file) = @_; my $old_xml_file = $xml_file; my ($sc_file, $sc_dir, $sc_ext); my ($file, $dir, $ext); # If user passed nothing or empty ("") check the script location with name # the same as the script unless (length $xml_file) { $xml_file = $Tharn::Scriptname; # get the last .thpl(or any ext) name and replace with .xml $xml_file =~ s/\.[^.]*/.xml/; return $xml_file if (-f $xml_file); NATE::BaseException->throw( "No Xml File was passed in and $xml_file " . "does not exist"); } # always ensure that a file ends with .xml if ($xml_file !~ /\.xml$/) { $xml_file .= ".xml"; } # -f checks to see if the file exists and is a plain file return $xml_file if (-f $xml_file); # second get the script location: dir:filename:ext # this regex will split something like # /u/mcclella/p4/test/san/sanity/testXml_path.xml # into # $sc_dir = /u/mcclella/p4/test/san/sanity # $sc_file = testXml_path # $sc_ext = xml ($sc_dir, $sc_file, $sc_ext) = $Tharn::Scriptname =~ m#(\S+)/(\S+)\.(\S+)$#; # Get file location info ($file, $dir, $ext) = &File::Basename::fileparse($xml_file, qr/\.[^.]*/); # if dir is not passed, grab it from script location if ($dir =~ /^.\/$|\.$|^\/$|""/) { $xml_file = "$sc_dir/"; } else { $xml_file = $dir; } # if file is not passed, grab it from script name unless (length $file) { $xml_file .= $sc_file; } else { $xml_file .= $file; } # add the extension back if (length $ext) { $xml_file .= $ext; } # -f checks to see if the file exists and is a plain file # If the file does not exist, this was the final location to check # and an error is returned. return $xml_file if (-f $xml_file); # Still have not found the file. Seems like we hit this when # $sc_dir is a relative path (smokes). # Next, call NATE::Inc::find_file() with the current # $xml_file and see if it can find it in the nate lib path. my @natelib; if (exists($ENV{"NATE_LIB"})) { @natelib = split(/;/, $ENV{"NATE_LIB"}); } if (not scalar(@natelib) and exists($ENV{"NATE_LIB_UNIX"})) { @natelib = split(/;/, $ENV{"NATE_LIB_UNIX"}); } $xml_file = NATE::Inc::find_file($xml_file, search => [@natelib]); return $xml_file if (-f $xml_file); # If we get here, we didn't find the file anywhere we know to look # get the cwd for the error message my $cwd = &Cwd::getcwd(); NATE::BaseException->throw( "$old_xml_file does not exist as a direct path " . "and $file" . "$ext" . " can not be found in the following:\n" . " - $cwd\n" . " - $sc_dir\n" . "NATELIB below\n" . " - " . join("\n - ", @natelib)); } # end of sub _get_xml_path ############################################################################### # Method: _get_ip_addresses # Objective: Generate a list of IP addresses based on an address range. # Inputs: A reference to a hash: # ip_data => contains address range for iSCSI lifs # Returns: A list or reference to a list containing IP addresses. ############################################################################### sub _get_ip_addresses { my ($self, $ip_data) = @_; $Log->enter() if $may_enter; # Locals my ($ip_range) = ""; my (@ips) = (); # If there's an ip range get it, otherwise return an empty list if (defined($ip_data->{IP})) { $ip_range = $ip_data->{IP}; } else { return (wantarray()) ? () : []; } # if the ip range contains a colon, treat it as an ipv6 range if ($ip_range =~ /:/) { my @chunks = split(/:/, $ip_range); my $last_chunk = $chunks[-1]; my $hex_regex = "[A-Fa-f0-9]"; if ($last_chunk =~ /\[($hex_regex+)-($hex_regex+)\]/) { my $range_base = hex($1); my $range_max = hex($2); for ( my $ip_segment = $range_base; $ip_segment <= $range_max; $ip_segment++ ) { my $stringified_chunk = sprintf("%x", $ip_segment); $chunks[-1] = $stringified_chunk; my $addr = join(":", @chunks); push(@ips, $addr); } } } else { # 10.10.10.[start-end] # 10.10.[start-end].[start-end] # 10.10.[start-end].10 # split ip_range into four bytes my ($first, $second, $third, $fourth) = split(/\./, $ip_range); # find the range in the third and fourth byte if ($third =~ /\[(\d+)-(\d+)\]/) { for (my $i = $1; $i <= $2; $i++) { # do we have the range in the forth byte as well? if ($fourth =~ /\[(\d+)-(\d+)\]/) { for (my $j = $1; $j <= $2; $j++) { push(@ips, "$first.$second.$i.$j"); } } else { # range in third byte only push(@ips, "$first.$second.$i.$fourth"); } } } elsif ($fourth =~ /\[(\d+)-(\d+)\]/) { # range in fourth byte only for (my $i = $1; $i <= $2; $i++) { push(@ips, "$first.$second.$third.$i"); } } } # Swapping each element in the array with another randomly selected element. # Fisher-Yates algorithm # # TODO : Remove the follwing code if find out that there is no # value added by changing the order of the IP addresses or if there # performance is needed to improve. for (my $i = scalar(@ips); --$i;) { my $j = int rand($i + 1); next if $i == $j; @ips[$i, $j] = @ips[$j, $i]; } $Log->exit() if $may_exit; return (wantarray()) ? @ips : \@ips; } # End _get_ip_addresses ############################################################################### # Method: _verify_resources_loaded # Objective: Checks that the load resource flag has been set, meaning that # the subruotine load_resources has been executed, meaning that # objects are in memory. # Input: No input. # Returns: TRUE or propagates an exception. ############################################################################### sub _verify_resources_loaded { my ($self) = @_; $Log->enter() if $may_enter; # If resources have not been loaded, load them before executing. if (!$self->{resources_loaded}) { $self->load_resources(); } $Log->exit() if $may_exit; return TRUE; } ## end sub _verify_resources_loaded ############################################################################### # Method: _retrieve_iscsi_global_options # Objective: Verify if the user entered any ISCSI GLOBAL OPTION in the # environment tag and retrieve those values. # Input: No input. # Returns: TRUE or propagates an exception. ############################################################################### sub _retrieve_iscsi_global_options { my ($self) = @_; $Log->enter() if $may_enter; # Get the policies for the environment and save them my $global_iscsi_options = &ParamsMgr::search( search_path => [], key => "ENVIRONMENT", key_filter => qr/^GLOBAL_ISCSI_OPTIONS$/ ); if (&ParamsMgr::error()) { $Log->exit() if $may_exit; my $errmsg = &ParamsMgr::errmsg(); NATE::BaseException->throw( "Failed to find ENVIRONMENT tag on the XML file: $errmsg"); } # We know that iSCSI GLOBAL OPTIONS have been defaulted or specified, so let's # find out if the ISNS address was provided if (defined($global_iscsi_options->{isns_server}) && ($global_iscsi_options->{isns_server} ne "")) { $self->{globals}->{iscsi}->{isns_server} = $global_iscsi_options->{isns_server}; } # Also let's find and save the CHAP USER NAMES if provided. NOTE, the # passwords would always be defined and not blank because they have a # default value on the DTD file. No need to save those values if (defined($global_iscsi_options->{chap_in_username}) && ($global_iscsi_options->{chap_in_username} ne "")) { $self->{globals}->{iscsi}->{chap_in_username} = $global_iscsi_options->{chap_in_username}; } if (defined($global_iscsi_options->{chap_out_username}) && ($global_iscsi_options->{chap_out_username} ne "")) { $self->{globals}->{iscsi}->{chap_out_username} = $global_iscsi_options->{chap_out_username}; } $Log->exit() if $may_exit; return TRUE; } ## end sub _retrieve_iscsi_global_options ############################################################################### # Method: _protocol_services_update # Objective: Verifies that the iSCSI, FCP, NFS, and CIFS services tags are created if # vserver information that is been worked has those flags enabled. # Also uses the GLOBAL OPTIONS values to create or update those # tags. # Input: The data hash that has the vserver xml information. # Returns: The data hash updated. ############################################################################### sub _protocol_services_update { my ($self, $data) = @_; $Log->enter() if $may_enter; # Locals my $iscsi_service_found = (exists($data->{ISCSI_SERVICE})) ? TRUE : FALSE; my $fcp_service_found = (exists($data->{FCP_SERVICE})) ? TRUE : FALSE; my $isns_service_found = (exists($data->{ISNS_SERVICE})) ? TRUE : FALSE; my $cifs_service_found = (exists($data->{CIFS_SERVICE})) ? TRUE : FALSE; my $nfs_service_found = (exists($data->{NFS_SERVICE})) ? TRUE : FALSE; # Start with FCP # If FCP_SERVICE @vserver is ENABLED, need to make sure the service # is in place. if ($data->{FCP_ENABLED}) { # If service was not provided by the user, create it. if (!$fcp_service_found) { $data->{FCP_SERVICE}->{NAME} = $data->{NAME}; $data->{FCP_SERVICE}->{'STATUS-ADMIN'} = 'up'; } # Propagate the ENABLED flag $data->{FCP_SERVICE}->{SERVICE_ENABLED} = TRUE; } # If FCP_SERVICE @vserver is NOT ENABLED else { if ($fcp_service_found) { # Disable the service flag $data->{FCP_SERVICE}->{SERVICE_ENABLED} = FALSE; } } # If iSCSI_SERVICE @vserver is ENABLED, we need to make sure the service # is in place. if ($data->{ISCSI_ENABLED}) { # If iSCSI SERVICE was not provided by the user, create it. if (!$iscsi_service_found) { $data->{ISCSI_SERVICE}->{NAME} = $data->{NAME}; $data->{ISCSI_SERVICE}->{'STATUS-ADMIN'} = 'up'; } # Propagate the ENABLED flag $data->{ISCSI_SERVICE}->{SERVICE_ENABLED} = TRUE; # If the ISNS SERVICE exists if ($isns_service_found) { # Propagate the ENABLED flag $data->{ISNS_SERVICE}->{SERVICE_ENABLED} = TRUE; # If the ADDRESS is NOT defined, use the provided ISNS_SERVER global # option if ( (!defined($data->{ISNS_SERVICE}->{ADDRESS})) && (defined($self->{globals}->{iscsi}->{isns_server}))) { $data->{ISNS_SERVICE}->{ADDRESS} = $self->{globals}->{iscsi}->{isns_server}; } } else { # If user provided an ISNS_SERVER global option, create an ISNS # service tag if (defined($self->{globals}->{iscsi}->{isns_server})) { # Propagate the ENABLED flag $data->{ISNS_SERVICE}->{SERVICE_ENABLED} = TRUE; $data->{ISNS_SERVICE}->{FORCE} = "true"; $data->{ISNS_SERVICE}->{ADDRESS} = $self->{globals}->{iscsi}->{isns_server}; } } } # If ISCSI_SERVICE @vserver is NOT ENABLED, disable iSCSI and ISNS services else { if ($iscsi_service_found) { # Disable the service flag $data->{ISCSI_SERVICE}->{SERVICE_ENABLED} = FALSE; } if ($isns_service_found) { # Disable the service flag $data->{ISNS_SERVICE}->{SERVICE_ENABLED} = FALSE; } } # If CIFS_SERVICE @vserver is ENABLED, need to make sure the service # is in place. if ($data->{CIFS_ENABLED}) { # If service was not provided by the user if (!$cifs_service_found) { # create a default cifs service tag $data->{CIFS_SERVICE}->{NAME} = $data->{NAME}; $data->{CIFS_SERVICE}->{'STATUS-ADMIN'} = 'up'; # use the global settings from the global_cifs_options tag } # Propagate the ENABLED flag $data->{CIFS_SERVICE}->{SERVICE_ENABLED} = TRUE; } else { # If CIFS_SERVICE @vserver is NOT ENABLED if ($cifs_service_found) { # Disable the service flag $data->{CIFS_SERVICE}->{SERVICE_ENABLED} = FALSE; } } # If NFS_SERVICE @vserver is ENABLED, need to make sure the service # is in place. if ($data->{NFS_ENABLED}) { # If service was not provided by the user if (!$nfs_service_found) { # create a default nfs service tag $data->{NFS_SERVICE}->{NAME} = $data->{NAME}; # use the global settings from the global_nfs_options tag } # Propagate the ENABLED flag $data->{NFS_SERVICE}->{SERVICE_ENABLED} = TRUE; } else { # If NFS_SERVICE @vserver is NOT ENABLED if ($nfs_service_found) { # Disable the service flag $data->{NFS_SERVICE}->{SERVICE_ENABLED} = FALSE; } } $Log->exit() if $may_exit; return (wantarray()) ? %{$data} : $data; } ## end sub _protocol_services_update ############################################################################### # Method: _log_performance # Objective: Logs performance data in the performance log # Input: time_difference - The time difference from start to finish # action - The action taken(setup,cleanup) # Returns: TRUE ############################################################################### sub _log_performance { my ($self, @opts) = @_; $Log->enter() if $may_enter; my %args = (); %args = Params::Validate::validate( @opts, { time_difference => {type => SCALAR, optional => FALSE}, action => {type => SCALAR, optional => FALSE}, } ); # Redirect the $Log calls to write to performance.log $Log->redirect(path => (Tharn::param('LOGDIR') . '/performance.log')); $EnGenXUtil::TOTAL_OBJ_COUNT += $EnGenXUtil::OBJ_COUNT; $EnGenXUtil::TOTAL_TIME += $args{time_difference}; $Log->comment($EnGenXUtil::OBJ_COUNT . " objects for controller " . $args{action} . " took " . $args{time_difference} . " seconds with an average of " . $args{time_difference} / $EnGenXUtil::OBJ_COUNT . " seconds per object "); $Log->comment( "A total of $EnGenXUtil::TOTAL_OBJ_COUNT objects for Controller " . "Provisioning so far took $EnGenXUtil::TOTAL_TIME seconds " . "with an average of " . $EnGenXUtil::TOTAL_TIME / $EnGenXUtil::TOTAL_OBJ_COUNT . " seconds per object "); $Log->comment( "Logging the following so that the performance log will PASS"); $Log->comment("END OF TEST \/ RESULTS SUMMARY\nNo results reported\n"); # Direct $Log calls to write back to the original log file $Log->reset_path(); $Log->exit() if $may_exit; return TRUE; } ## end sub _log_performance sub _config { my ($self) = @_; &SubIPC::remove_subtest(); my ($st_status, $subtest); # Get the set of actions parameters from ParamsMgr my $actions = &ParamsMgr::search(search_path => [], key => "ACTIONS"); if (&ParamsMgr::error()) { $self->_set_err(&ParamsMgr::get_error()); return FALSE; } # Check to see if user wants to run config at all if ($actions->{CONFIG} == 0) { $Log->comment("Skipping config due to actions flag set to \"0\""); return TRUE; } # Verify resources have been loaded. $self->_verify_resources_loaded(); ############################################## # Controller config ############################################## $self->{sm_controller_object}->_config( run_id_prefix => $self->{run_id_prefix}, resource_list => \%{$self->{resource_list}} ); return TRUE; } ## end sub _config 1;