blob: ff93df3b3a25c015875695eb680a83e32de949f5 [file] [log] [blame]
#!/usr/bin/perl -w
###############################################################################
# $Id$
###############################################################################
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###############################################################################
=head1 NAME
VCL::Module - VCL base module
=head1 SYNOPSIS
In a derived module:
use base qw(VCL::Module);
sub initialize {
my $self = shift;
my $image_id = $self->data->get_image_id();
<perform module initialization tasks...>
return 1;
}
=head1 DESCRIPTION
C<VCL::Module> is the base class for the modularized VCL architecture. All VCL
modules should inherit from C<VCL::Module> or from another class which inherits
from C<VCL::Module> (multilevel inheritance).
To inherit directly from C<VCL::Module>:
C<use base qw(VCL::Module);>
To inherit from a class which ultimately inherits from C<VCL::Module>:
C<use base qw(VCL::Module::OS::Windows);>
C<VCL::Module> provides a common constructor which all derived modules should
use. Derived modules should not implement their own constructors. The
constructor provides derived modules the ability to implement an C<initialize()>
subroutine which will be automatically called when a derived module object is
created. This method should be used if a module needs to perform any functions
to initialize a newly created module object.
Modules derived from C<VCL::Module> have access to the common backend
reservation data API to access and set the data for the reservation being
processed via C<< $self->data >>. (C<$self> being a reference to a derived
module object)
=cut
##############################################################################
package VCL::Module;
# Specify the lib path using FindBin
use FindBin;
use lib "$FindBin::Bin/..";
# Configure inheritance
use base qw();
# Specify the version of this module
our $VERSION = '2.00';
# Specify the version of Perl to use
use 5.008000;
use strict;
use warnings;
use diagnostics;
use English '-no_match_vars';
use Digest::SHA1 qw(sha1_hex);
use VCL::utils;
use VCL::DataStructure;
use VCL::Module::Semaphore;
##############################################################################
=head1 CONSTRUCTOR
=cut
#/////////////////////////////////////////////////////////////////////////////
=head2 new
Parameters : Hash reference - hash must contain a key called data_structure.
The value of this key must be a reference to a VCL::DataStructure
object.
Returns : Success - new object which inherits from VCL::Module
Failure - undefined
Description : Constructor for VCL modules. All VCL modules should use this
constructor. Objects created using this constructor have a base
class of VCL::Module. A module may have other intermediate
classes it is derived from if multilevel inheritance is used.
This constructor must be passed a reference to a previously
created VCL::DataStructure object. Derived objects will have
access to the data() object method: $self->data->get...()
During object creation, this constructor will attempt to call an
initialize() subroutine defined in a child class. This allows
tasks to be automatically performed during object creation.
Implementing an initialize() subroutine is optional.
Any arguments passed to new() will be passed unchanged to
initialize().
Example:
use VCL::Module::TestModule;
my $test_module = new VCL::Module::TestModule({data_structure => $self->data});
=cut
sub new {
my $class = shift;
my $args = shift;
notify($ERRORS{'DEBUG'}, 0, "constructor called, class=$class");
# Create a variable to store the newly created class object
my $class_object;
# Make sure the data structure was passed as an argument called 'data_structure'
if (!defined $args->{data_structure}) {
notify($ERRORS{'CRITICAL'}, 0, "required 'data_structure' argument was not passed");
return;
}
# Make sure the 'data_structure' argument contains a VCL::DataStructure object
if (ref $args->{data_structure} ne 'VCL::DataStructure') {
notify($ERRORS{'CRITICAL'}, 0, "'data_structure' argument passed is not a reference to a VCL::DataStructure object");
return;
}
# Add the DataStructure reference to the class object
$class_object->{data} = $args->{data_structure};
# Bless the object as the class which new was called with
bless $class_object, $class;
notify($ERRORS{'DEBUG'}, 0, "$class object created");
# Check if not running in setup mode and if initialize() subroutine is defined for this module
if (!$SETUP_MODE && $class_object->can("initialize")) {
# Call the initialize() subroutine, if it returns 0, return 0
# If it doesn't return 0, return the object reference
return if (!$class_object->initialize($args));
}
return $class_object;
} ## end sub new
##############################################################################
=head1 OBJECT METHODS
=cut
#/////////////////////////////////////////////////////////////////////////////
=head2 data
Parameters : None
Returns : Reference to the DataStructure object
Description : This subroutine allows VCL module objects to retrieve data using
the object's DataStructure object as follows:
my $image_id = $self->data->get_image_id();
=cut
sub data {
my $self = shift;
# If this was called as a class method, return the DataStructure object stored in the class object
return $self->{data} if ref($self);
# Not called as a class method, check to see if $ENV{data} is defined
return $ENV{data} if (defined($ENV{data}) && $ENV{data});
# $ENV{data} is not set, set it
$ENV{data} = new VCL::DataStructure();
# Return the new DataStructure if got created successfully
return $ENV{data} if (defined($ENV{data}) && $ENV{data});
notify($ERRORS{'CRITICAL'}, 0, "unable to create DataStructure object");
return 0;
} ## end sub data
#/////////////////////////////////////////////////////////////////////////////
=head2 get_package_hierarchy
Parameters : String containing the name of a Perl package
(note: parameter is optional if called as an object method,
required if called as a class function
Returns : Array containing class package names
Description : Determines the Perl package inheritance hierarchy given a
package name or object reference.
Returns an array containing the names of the originating
Perl package and any parent packages it inherits from.
This subroutine does not support multiple inheritance.
If any package up the chain inherits from multiple classes,
only the first class listed in the package's @ISA array is
used.
The package name on which this subroutine is called is the
lowest in the hierarchy and has the lowest index in the
array.
If the package on which this subroutine is called does not
explicitly inherit from any other packages, the array
returned will only contain 1 element which is the calling
package name.
Example: call as object method:
my $os = VCL::Module::OS::Windows::Version_5::XP->new({data_structure => $self->data});
my @packages = $os->get_package_hierarchy();
Example: call as class function:
my @packages = get_package_hierarchy("VCL::Module::OS::Windows::Version_5::XP");
Both examples return the following array:
[0] = 'VCL::Module::OS::Windows::Version_5::XP'
[1] = 'VCL::Module::OS::Windows::Version_5'
[2] = 'VCL::Module::OS::Windows'
[3] = 'VCL::Module::OS'
[4] = 'VCL::Module'
=cut
sub get_package_hierarchy {
my $argument = shift;
if (!$argument) {
notify($ERRORS{'WARNING'}, 0, "subroutine was not called as an object method and argument was not passed");
return;
}
my @return_package_names;
my $package_name;
# Check if this was called as an object method
# If it was, check if an argument was supplied
if (ref($argument) && $argument->isa('VCL::Module')) {
my $argument2 = shift;
# If called as object method and argument was supplied, use the argument
$argument = $argument2 if defined($argument2);
}
# Check if argument is an object reference or a package name string
if (ref($argument)) {
# Argument is a reference, get package hierarchy of object type which called this
# Add the calling package name as the first element of the return array
$package_name = ref($argument);
push @return_package_names, $package_name;
}
else {
# Argument is not a reference, assume argument is a string containing a package name
$package_name = $argument;
}
notify($ERRORS{'DEBUG'}, 0, "finding package hierarchy for: $package_name");
# Use eval to retrieve the package name's @ISA array
my @package_isa = eval '@' . $package_name . '::ISA';
if ($EVAL_ERROR) {
notify($ERRORS{'WARNING'}, 0, "unable to determine \@ISA array for package: $package_name, error:\n$EVAL_ERROR");
return;
}
# Get the number of elements in the package's @ISA array
my $package_isa_count = scalar @package_isa;
# Check if @ISA is empty
if ($package_isa_count == 0) {
notify($ERRORS{'DEBUG'}, 0, "$package_name has no parent packages");
return ();
}
notify($ERRORS{'DEBUG'}, 0, "parent package names for $package_name:\n" . format_data(\@package_isa));
my $parent_package_name = $package_isa[0];
# Warn if package uses multiple inheritance, only use 1st element of package's @ISA array
if ($package_isa_count > 1) {
notify($ERRORS{'WARNING'}, 0, "$package_name has multiple parent packages, only using $parent_package_name");
}
# Add this package's parent package name to the return array
push @return_package_names, $parent_package_name;
# Recursively call this sub on the parent package and add the results to the return array
push @return_package_names, get_package_hierarchy($parent_package_name);
notify($ERRORS{'DEBUG'}, 0, "returning for $package_name:\n" . format_data(\@return_package_names));
return @return_package_names;
}
#/////////////////////////////////////////////////////////////////////////////
=head2 code_loop_timeout
Parameters : 1: code reference
2: array reference containing arguments to pass to code reference
3: message to display when attempting to execute code reference
4: timeout seconds, maximum number of seconds to attempt to execute code until it returns true
5: seconds to wait in between code execution attempts
Returns : If code returns true: 1
If code never returns true: 0
Description : Executes the code contained in the code reference argument until
it returns true or until the timeout is reached.
Example:
Call the _pingnode subroutine, pass it a single argument,
continue calling _pingnode until 20 seconds have passed, wait 4
seconds in between attempts:
$self->os->code_loop_timeout(\&_pingnode, ['vclh3-8'], 'checking ping', 20, 4);
=cut
sub code_loop_timeout {
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;
}
# Get the start time
my $start_time = time();
my $computer_node_name = $self->data->get_computer_node_name();
# Check the argument count and get the arguments
if (scalar(@_) != 5) {
notify($ERRORS{'WARNING'}, 0, scalar(@_) . " arguments were passed, argument count must be 5");
return;
}
my ($code_ref, $args_array_ref, $message, $total_wait_seconds, $attempt_delay_seconds) = @_;
# Make sure the code reference argument was passed correctly
if (!defined($code_ref)) {
notify($ERRORS{'WARNING'}, 0, "code reference argument is undefined");
return;
}
elsif (ref($code_ref) ne 'CODE') {
notify($ERRORS{'WARNING'}, 0, "1st argument must be a code reference, not " . ref($code_ref));
return;
}
if (!defined($args_array_ref)) {
notify($ERRORS{'WARNING'}, 0, "2nd argument (arguments to pass to code reference) is undefined");
return;
}
elsif (!ref($args_array_ref) || ref($args_array_ref) ne 'ARRAY') {
notify($ERRORS{'WARNING'}, 0, "2nd argument (arguments to pass to code reference) is not an array reference");
return;
}
if (!defined($message)) {
notify($ERRORS{'WARNING'}, 0, "3nd argument (message to display) is undefined");
return;
}
elsif (!$message) {
$message = 'executing code reference';
}
if (!defined($total_wait_seconds) || $total_wait_seconds !~ /^\d+$/) {
notify($ERRORS{'WARNING'}, 0, "4th argument (total wait seconds) was not passed correctly");
return;
}
if (!defined($attempt_delay_seconds) || $attempt_delay_seconds !~ /^\d+$/) {
notify($ERRORS{'WARNING'}, 0, "5th argument (attempt delay) was not passed correctly");
return;
}
# Calculate total seconds to wait and end time
my $end_time = $start_time + $total_wait_seconds;
notify($ERRORS{'OK'}, 0, "$message, maximum of $total_wait_seconds seconds");
# Loop until code returns true
# Loop once if the wait time is 0
my $attempt_count = 0;
my $current_time;
while (($current_time = time()) < $end_time || ($total_wait_seconds == 0 && $attempt_count == 0)) {
$attempt_count++;
if ($attempt_count > 1) {
my $seconds_elapsed = $current_time - $start_time;
my $seconds_remaining = $end_time - $current_time;
notify($ERRORS{'OK'}, 0, "attempt " . ($attempt_count-1) . ": code returned false, seconds elapsed/remaining: $seconds_elapsed/$seconds_remaining, sleeping for $attempt_delay_seconds seconds");
sleep $attempt_delay_seconds;
}
notify($ERRORS{'OK'}, 0, "attempt $attempt_count: $message");
if (&$code_ref(@{$args_array_ref})) {
notify($ERRORS{'OK'}, 0, "$message, code returned true");
return 1;
}
}
notify($ERRORS{'OK'}, 0, "$message, code did not return true after waiting $total_wait_seconds seconds");
return 0;
} ## end sub code_loop_timeout
#/////////////////////////////////////////////////////////////////////////////
=head2 get_semaphore
Parameters : $file_path, $total_wait_seconds (optional), $attempt_delay_seconds (optional)
Returns : VCL::Module::Semaphore object
Description : This subroutine is used to ensure that only 1 process performs a
particular task at a time. An example would be the retrieval of
an image from another management node. If multiple reservations
are being processed for the same image, each reservation may
attempt to retrieve it via SCP at the same time. This subroutine
can be used to only allow 1 process to retrieve the image. The
others will wait until the semaphore is released by the
retrieving process.
Attempts to open and obtain an exclusive lock on the file
specified by the file path argument. If unable to obtain an
exclusive lock, it will wait up to the value specified by the
total wait seconds argument (default: 30 seconds). The number of
seconds to wait in between retries can be specified (default: 15
seconds).
A semaphore object is returned. The exclusive lock will be
retained as long as the semaphore object remains defined. Once
undefined, the exclusive lock is released and the file is
deleted.
Examples:
Semaphore is released when it is undefined:
my $semaphore = $self->get_semaphore('/tmp/test.lock');
... <exclusive lock is in place>
undef $semaphore;
... <exclusive lock released>
Semaphore is released when it goes out of scope:
if (blah) {
my $semaphore = $self->get_semaphore('/tmp/test.lock');
... <exclusive lock is in place>
}
... <exclusive lock released>
=cut
sub get_semaphore {
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;
}
# Get the file path argument
my ($file_path, $total_wait_seconds, $attempt_delay_seconds) = @_;
if (!$file_path) {
notify($ERRORS{'WARNING'}, 0, "file path argument was not supplied");
return;
}
# Attempt to create a new semaphore object
my $semaphore = VCL::Module::Semaphore->new({'data_structure' => $self->data});
if (!$semaphore) {
notify($ERRORS{'WARNING'}, 0, "failed to create semaphore object");
return;
}
# Attempt to open and exclusively lock the file
if ($semaphore->get_lockfile($file_path, $total_wait_seconds, $attempt_delay_seconds)) {
# Return the semaphore object
return $semaphore;
}
else {
notify($ERRORS{'DEBUG'}, 0, "failed to open and optain exclusive lock on file: $file_path");
return;
}
}
#/////////////////////////////////////////////////////////////////////////////
=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__
=head1 SEE ALSO
L<http://cwiki.apache.org/VCL/>
=cut