Author: arkurth Date: Tue Sep 14 19:15:54 2010 New Revision: 997046 URL: http://svn.apache.org/viewvc?rev=997046&view=rev Log: VCL-164 Added setup() and setup_add_local_account() to Module.pm to allow local accounts to be added via 'vcld -setup'. Updated utils.pm::get_user_info() to accept an optional affiliation identifier argument because. The user.unityid column is not unique, but user.unityid+user.affiliationid must be unique. This argument allows the affiliation to be specified so that a single user is found even if the same unityid is used for different affiliations.
Other Changed utils.pm::format_data to use Data::Dumper. The old subroutine was very difficult to debug/maintain and Data::Dumper performs the same function. Removed progressive delay in between failed SSH attempts in utils.pm::run_ssh_command. The progressive delay never added any benefit, but delayed a process before it failed. Modified: incubator/vcl/trunk/managementnode/lib/VCL/Module.pm incubator/vcl/trunk/managementnode/lib/VCL/utils.pm Modified: incubator/vcl/trunk/managementnode/lib/VCL/Module.pm URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/Module.pm?rev=997046&r1=997045&r2=997046&view=diff ============================================================================== --- incubator/vcl/trunk/managementnode/lib/VCL/Module.pm (original) +++ incubator/vcl/trunk/managementnode/lib/VCL/Module.pm Tue Sep 14 19:15:54 2010 @@ -83,8 +83,9 @@ use strict; use warnings; use diagnostics; use English '-no_match_vars'; +use Digest::SHA1 qw(sha1_hex); -use VCL::utils qw($SETUP_MODE $VERBOSE %ERRORS ¬ify &getnewdbh format_data); +use VCL::utils; use VCL::DataStructure; use VCL::Module::Semaphore; @@ -499,6 +500,176 @@ sub get_semaphore { #///////////////////////////////////////////////////////////////////////////// +=head2 setup + + Parameters : none + Returns : + Description : This subroutine is used when vcld is run in setup mode. It + presents a menu for overall VCL configuration settings. + +=cut + +sub setup { + my $self = shift; + unless (ref($self) && $self->isa('VCL::Module')) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + push @{$ENV{setup_path}}, 'User Accounts'; + + my @operation_choices = ( + 'Add Local VCL User Account', + ); + + my @setup_path = @{$ENV{setup_path}}; + OPERATION: while (1) { + @{$ENV{setup_path}} = @setup_path; + + print '-' x 76 . "\n"; + + print "Choose an operation:\n"; + my $operation_choice_index = setup_get_array_choice(@operation_choices); + last if (!defined($operation_choice_index)); + my $operation_name = $operation_choices[$operation_choice_index]; + print "\n"; + + push @{$ENV{setup_path}}, $operation_name; + + if ($operation_name =~ /add local/i) { + $self->setup_add_local_account(); + } + } + + pop @{$ENV{setup_path}}; + return 1; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 setup_add_local_account + + Parameters : none + Returns : boolean + Description : Presents an interface to create a local VCL user account. This + subroutine is executed when vcld is run with the -setup argument. + +=cut + +sub setup_add_local_account { + my $self = shift; + unless (ref($self) && $self->isa('VCL::Module')) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + #myusername', 'myfirstname', 'mylastname', 'myemailaddr', + + # Get the username (user.unityid) + my $username; + while (!$username) { + $username = setup_get_input_string("Enter the user login name"); + return if (!defined($username)); + + # Check format of username + if ($username !~ /^[\w\-_]+$/i) { + print "User name is not valid: '$username'\n\n"; + $username = undef; + } + + # Make sure username does not already exist + my $user_info = get_user_info($username, 'Local'); + if ($user_info && $user_info->{unityid} eq $username) { + print "Local VCL user account already exists: $username\n\n"; + $username = undef; + } + } + print "\n"; + + # Get the other required information + my $first_name; + while (!$first_name) { + $first_name = setup_get_input_string("Enter the first name"); + return if (!defined($first_name)); + } + print "\n"; + + my $last_name; + while (!$last_name) { + $last_name = setup_get_input_string("Enter the last name"); + return if (!defined($last_name)); + } + print "\n"; + + my $email_address; + while (!defined($email_address)) { + $email_address = setup_get_input_string("Enter the email address", 'not set'); + return if (!defined($email_address)); + + # Check format of the email address + if ($email_address eq 'not set') { + $email_address = ''; + } + elsif ($email_address !~ /^([a-z0-9._%+...@[a-z0-9.-]+\.[a-z]{2,4}(,?))+$/i) { + print "Email address is not valid: '$email_address'\n\n"; + $email_address = undef; + } + } + print "\n"; + + my $password; + while (!$password) { + $password = setup_get_input_string("Enter the password"); + return if (!defined($password)); + } + print "\n"; + + # Generate an 8-character random string + my @characters = ("a" .. "z", "A" .. "Z", "0" .. "9"); + my $random_string; + srand; + for (1 .. 8) { + $random_string .= $characters[rand((scalar(@characters) - 1))]; + } + + # Get an SHA1 hex digest from the password and random string + my $digest = sha1_hex("$password$random_string"); + + # Insert a row into the user table + my $insert_user_statement = <<EOF; +INSERT INTO user +(unityid, affiliationid, firstname, lastname, email, lastupdated) +VALUES +('$username', (SELECT id FROM affiliation WHERE name LIKE 'Local'), '$first_name', '$last_name', '$email_address', NOW()) +EOF + + my $user_id = database_execute($insert_user_statement); + if (!defined($user_id)) { + print "ERROR: failed to insert into user table\n"; + return; + } + + # Insert a row into the localauth table + my $insert_localauth_statement = <<EOF; +INSERT INTO localauth +(userid, passhash, salt, lastupdated) +VALUES +($user_id, '$digest', '$random_string', NOW()) +EOF + + my $localauth_id = database_execute($insert_localauth_statement); + if (!defined($localauth_id)) { + print "ERROR: failed to insert into localauth table\n"; + return; + } + + print "Local VCL user account successfully created: $username\n"; + + return 1; +} + +#///////////////////////////////////////////////////////////////////////////// + 1; __END__ Modified: incubator/vcl/trunk/managementnode/lib/VCL/utils.pm URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/utils.pm?rev=997046&r1=997045&r2=997046&view=diff ============================================================================== --- incubator/vcl/trunk/managementnode/lib/VCL/utils.pm (original) +++ incubator/vcl/trunk/managementnode/lib/VCL/utils.pm Tue Sep 14 19:15:54 2010 @@ -70,6 +70,7 @@ use List::Util qw(min max); use HTTP::Headers; use RPC::XML::Client; use Scalar::Util 'blessed'; +use Data::Dumper; #use Date::Calc qw(Delta_DHMS Time_to_Date Date_to_Time); @@ -5422,16 +5423,9 @@ sub run_ssh_command { # Delay performing next attempt if this isn't the first attempt if ($attempts > 1) { - my $delay; - if ($attempts == 2) { - $delay = 2; - } - else { - # Progressively increase the delay - $delay = (5 * $attempts); - } - notify($ERRORS{'DEBUG'}, 0, "sleeping for $delay seconds before making next SSH attempt") if $output_level; - sleep $delay; + my $delay_seconds = 2; + notify($ERRORS{'DEBUG'}, 0, "sleeping for $delay_seconds seconds before making next SSH attempt") if $output_level; + sleep $delay_seconds; } ## Add -v (verbose) argument to command if this is the 2nd attempt @@ -7918,7 +7912,7 @@ sub get_computer_grp_members { =head2 get_user_info - Parameters : $user_identifier + Parameters : $user_identifier, $affiliation_identifier (optional) Returns : hash reference Description : Retrieves user information from the database. The user identifier argument can either be a user ID or unityid. A hash reference is @@ -7961,13 +7955,13 @@ sub get_computer_grp_members { =cut sub get_user_info { - my ($user_identifier) = @_; + my ($user_identifier, $affiliation_identifier) = @_; if (!defined($user_identifier)) { notify($ERRORS{'WARNING'}, 0, "user identifier argument was not specified"); return; } - - my $select_statement = <<EOF; + + my $select_statement = <<EOF; SELECT DISTINCT user.*, adminlevel.name AS adminlevel_name, @@ -7985,13 +7979,25 @@ LEFT JOIN (affiliation) ON (affiliation. LEFT JOIN (IMtype) ON (IMtype.id = user.IMtypeid) WHERE EOF - + + # If the user identifier is all digits match it to user.id + # Otherwise, match user.unityid if ($user_identifier =~ /^\d+$/) { $select_statement .= "user.id = $user_identifier"; } else { $select_statement .= "user.unityid = '$user_identifier'"; } + + # If the affiliation identifier argument was specified add affiliation table clause + if (defined($affiliation_identifier)) { + if ($affiliation_identifier =~ /^\d+$/) { + $select_statement .= "\nAND affiliation.id = $affiliation_identifier"; + } + else { + $select_statement .= "\nAND affiliation.name LIKE '$affiliation_identifier'"; + } + } # Call the database select subroutine # This will return an array of one or more rows based on the select statement @@ -8631,98 +8637,29 @@ sub update_cluster_info { =head2 format_data - Parameters : - Returns : 0 or 1 - Description : + Parameters : $data + Returns : string + Description : Formats the data argument using Data::Dumper. =cut sub format_data { + my @data = @_; - my $return_string; - - my $level = 0; - $level = $_[scalar(@_) - 2] if (scalar(@_) > 2 && !ref($_[scalar(@_) - 2])); - - my $name = ''; - $name = $_[scalar(@_) - 1] if (scalar(@_) > 1 && !ref($_[scalar(@_) - 1])); - - my $type; - my $data; - - if (ref($_[0]) eq "HASH" || (blessed($_[0]) && $_[0]->isa("HASH"))) { - $data = $_[0]; - $type = '%'; - return "%<empty>" if (keys(%{$_[0]}) == 0); + if (!(@data)) { + return '<undefined>'; } - elsif (ref($_[0]) eq "ARRAY" || (blessed($_[0]) && $_[0]->isa("ARRAY"))) { - my $index = 0; - for (@{$_[0]}) { - $data->{$index} = $_; - $index++; - } - $type = '@'; - return "@<empty>" if (@{$_[0]} == 0); - } - elsif (ref($_[0]) eq "SCALAR") { - $data = $_[0]; - $type = '$'; - } - else { - $data = \$_[0]; - $type = '$'; - - $return_string .= "ref: " . ref($_[0]) . "\n"; - $return_string .= "data: " . $_[0] . "\n"; - - return $return_string; - } - - $data = 'NULL' if (!defined $data); - - $return_string .= "$type$name\n"; - - # Loop through values - foreach my $key (sort {lc($a) cmp lc($b)} keys(%{$data})) { - my $value = $data->{$key}; - - $value = 'NULL' if (!defined $value); - - for (my $count = 0; $count < $level; $count++) { - $return_string .= "..." if ($count < $level); - } - $return_string .= "|--"; - - if (ref($value) eq 'SCALAR') { - $value = "\\'$$value'"; - } - elsif (!ref($value) && $value ne 'NULL') { - $value = "'$value'"; - } - - if (!ref($value)) { - if ($type eq '@') { - $return_string .= "[$key] = $value\n"; - } - elsif ($type eq '%') { - $return_string .= "[$name]{$key} = $value\n"; - } - } - else { - if ($type eq '@') { - #$return_string .= "\[$key\]\n"; - $return_string .= format_data($value, $level + 1, "$name\[$key\]"); - } - elsif ($type eq '%') { - #$return_string .= "\{$key\} $name\n"; - $return_string .= format_data($value, $level + 1, "$name\{$key\}"); - } - } ## end else [ if (!ref($value)) - - } ## end foreach my $key (sort {lc($a) cmp lc($b)} keys(... - - return $return_string; -} ## end sub format_data + + $Data::Dumper::Indent = 1; + $Data::Dumper::Purity = 1; + $Data::Dumper::Useqq = 1; # Use double quotes for representing string values + $Data::Dumper::Terse = 1; + $Data::Dumper::Quotekeys = 1; # Quote hash keys + $Data::Dumper::Pair = ' => '; # Specifies the separator between hash keys and values + $Data::Dumper::Sortkeys = 1; # Hash keys are dumped in sorted order + + return Dumper(@data); +} #/////////////////////////////////////////////////////////////////////////////