You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@vcl.apache.org by ar...@apache.org on 2011/10/05 20:22:54 UTC

svn commit: r1179363 - in /incubator/vcl/trunk/managementnode/lib/VCL: DataStructure.pm Module/OS.pm Module/OS/Linux.pm utils.pm

Author: arkurth
Date: Wed Oct  5 18:22:54 2011
New Revision: 1179363

URL: http://svn.apache.org/viewvc?rev=1179363&view=rev
Log:
VCL-30
Updated SQL query utils.pm::get_connect_methods subroutine in utils.pm and renamed it to get_connect_method_info to match other subroutines. Updated OS.pm::process_connect_methods to handle the results returned by get_connect_method_info.

Updated Linux.pm::enable_firewall_port to accept the same arguments and scope format as Windows.pm::enable_firewall_port.

VCL-503
Added OS.pm::execute_new. This isn't currently called by anything and will eventually replace OS.pm::execute. It uses Net::SSH::Expect to keep an SSH connection open and then commands are issued to this connection. This speeds things up. There are commented out "return...execute_new" lines at the beginning of OS.pm::execute and in utils.pm::run_ssh_command. Uncommenting these lines will cause the new subroutine to be used.

Other
Added OS type information to the hash returned by get_image_info.  Added OS.pm::get_management_node_identity_key_paths subroutine so that the comma-separated value is parsed consistently.

Modified:
    incubator/vcl/trunk/managementnode/lib/VCL/DataStructure.pm
    incubator/vcl/trunk/managementnode/lib/VCL/Module/OS.pm
    incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm
    incubator/vcl/trunk/managementnode/lib/VCL/utils.pm

Modified: incubator/vcl/trunk/managementnode/lib/VCL/DataStructure.pm
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/DataStructure.pm?rev=1179363&r1=1179362&r2=1179363&view=diff
==============================================================================
--- incubator/vcl/trunk/managementnode/lib/VCL/DataStructure.pm (original)
+++ incubator/vcl/trunk/managementnode/lib/VCL/DataStructure.pm Wed Oct  5 18:22:54 2011
@@ -2319,6 +2319,28 @@ sub get_management_node_public_dns_serve
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 get_management_node_identity_key_paths
+
+ Parameters  : None
+ Returns     : If successful: array containing paths to SSH identity keys
+               If failed: false
+ Description : Returns an array containing the paths to SSH identity keys
+               configured for the management node.
+
+=cut
+
+sub get_management_node_identity_key_paths {
+	my $keys_string = $ENV{management_node_info}{keys};
+	if (!$keys_string) {
+		notify($ERRORS{'WARNING'}, 0, "no identity key paths are configured for the management node");
+		return ();
+	}
+	
+	return split(/\s*[,;]\s*/, $keys_string);
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 =head2 get_computer_state_name
 
  Parameters  : computer name (optional

Modified: incubator/vcl/trunk/managementnode/lib/VCL/Module/OS.pm
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/Module/OS.pm?rev=1179363&r1=1179362&r2=1179363&view=diff
==============================================================================
--- incubator/vcl/trunk/managementnode/lib/VCL/Module/OS.pm (original)
+++ incubator/vcl/trunk/managementnode/lib/VCL/Module/OS.pm Wed Oct  5 18:22:54 2011
@@ -52,6 +52,8 @@ use strict;
 use warnings;
 use diagnostics;
 use English '-no_match_vars';
+use Net::SSH::Expect;
+
 use VCL::utils;
 
 ##############################################################################
@@ -547,7 +549,7 @@ sub wait_for_response {
 	my $end_time = time();
 	my $duration = ($end_time - $start_time);
 	
-	#insertloadlog($reservation_id, $computer_id, "osrespond", "$computer_node_name is responding to SSH after $duration seconds");
+	# insertloadlog($reservation_id, $computer_id, "osrespond", "$computer_node_name is responding to SSH after $duration seconds");
 	notify($ERRORS{'OK'}, 0, "$computer_node_name is responding to SSH after $duration seconds");
 	return 1;
 }
@@ -1577,6 +1579,7 @@ sub create_text_file {
 =cut
 
 sub execute {
+#return execute_new(@_);
 	my $self = shift;
 	unless (ref($self) && $self->isa('VCL::Module')) {
 		notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as an object method");
@@ -1615,6 +1618,177 @@ sub execute {
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 execute_new
+
+ Parameters  : $computer_name (conditional), $command, $display_output, $timeout_seconds, $max_attempts, $port, $user, $password
+ Returns     : array ($exit_status, $output)
+ Description : Executes a command on the computer via SSH.
+
+=cut
+
+sub execute_new {
+	my $argument = shift;
+	my ($computer_name, $command, $display_output, $timeout_seconds, $max_attempts, $port, $user, $password);
+	
+	if (!ref($argument) || $argument->isa('VCL::Module')) {
+		if ($argument->isa('VCL::Module')) {
+			$computer_name = $argument->data->get_computer_node_name();
+		}
+		else {
+			$computer_name = $argument;
+		}
+		($command, $display_output, $timeout_seconds, $max_attempts, $port, $user, $password) = @_;
+	}
+	else {
+		$computer_name = $argument->{node};
+		$command = $argument->{command};
+		$display_output = $argument->{display_output};
+		$timeout_seconds = $argument->{timeout};
+		$max_attempts = $argument->{max_attempts};
+		$port = $argument->{port};
+		$user = $argument->{user};
+		$password = $argument->{password};
+	}
+	
+	if (!$computer_name) {
+		notify($ERRORS{'WARNING'}, 0, "computer name could not be determined");
+		return;
+	}
+	if (!$command) {
+		notify($ERRORS{'WARNING'}, 0, "command argument was not specified");
+		return;
+	}
+	
+	$display_output = 0 unless $display_output;
+	$timeout_seconds = 60 unless $timeout_seconds;
+	$max_attempts = 1 unless $max_attempts;
+	$port = 22 unless $port;
+	$user = 'root' unless $user;
+	
+	# Override the die handler
+	local $SIG{__DIE__} = sub{};
+	
+	my $ssh;
+	my $attempt = 0;
+	my $attempt_delay = 5;
+	my $attempt_string = '';
+	
+	ATTEMPT: while ($attempt < $max_attempts) {
+		if ($attempt > 0) {
+			$attempt_string = "attempt $attempt/$max_attempts: ";
+			$ssh->close() if $ssh;
+			
+			notify($ERRORS{'DEBUG'}, 0, $attempt_string . "sleeping for $attempt_delay seconds before making next attempt");
+			sleep $attempt_delay;
+		}
+		
+		$attempt++;
+		$attempt_string = "attempt $attempt/$max_attempts: " if ($attempt > 1);
+		
+		
+		if (!$ENV{net_ssh_expect}{$computer_name}) {
+			eval {
+				$ssh = Net::SSH::Expect->new(
+					host => $computer_name,
+					user => $user,
+					port => $port,
+					raw_pty => 1,
+					no_terminal => 1,
+					ssh_option => '-o StrictHostKeyChecking=no',
+					timeout => 3,
+				);
+				
+				if ($ssh) {
+					notify($ERRORS{'DEBUG'}, 0, "created " . ref($ssh) . " object to control $computer_name");
+				}
+				else {
+					notify($ERRORS{'WARNING'}, 0, "failed to create Net::SSH::Expect object to control $computer_name, $!");
+					next ATTEMPT;
+				}
+				
+				if ($ssh->run_ssh()) {
+					notify($ERRORS{'DEBUG'}, 0, ref($ssh) . " object forked SSH process to control $computer_name");
+				}
+				else {
+					notify($ERRORS{'WARNING'}, 0, ref($ssh) . " object failed to fork SSH process to control $computer_name, $!");
+					next ATTEMPT;
+				}
+				
+				#$ssh->exec("stty -echo");
+				#$ssh->exec("stty raw -echo");
+				
+				# Set the timeout counter behaviour:
+				# If true, sets the timeout to "inactivity timeout"
+				# If false sets it to "absolute timeout"
+				#$ssh->restart_timeout_upon_receive(1);
+			};
+			if ($EVAL_ERROR) {
+				if ($EVAL_ERROR =~ /^(\w+) at \//) {
+					notify($ERRORS{'DEBUG'}, 0, $attempt_string . "$1 error occurred initializing Net::SSH::Expect object for $computer_name");
+				}
+				else {
+					notify($ERRORS{'DEBUG'}, 0, $attempt_string . "error occurred initializing Net::SSH::Expect object for $computer_name");
+				}
+				next ATTEMPT;
+			}
+		}
+		else {
+			$ssh = $ENV{net_ssh_expect}{$computer_name};
+			
+			# Delete the stored SSH object to make sure it isn't saved if the command fails
+			# The SSH object will be added back to %ENV if the command completes successfully
+			delete $ENV{net_ssh_expect}{$computer_name};
+		}
+		
+		notify($ERRORS{'DEBUG'}, 0, $attempt_string . "executing command on $computer_name: '$command', timeout: $timeout_seconds seconds") if ($display_output);
+		$ssh->send($command . ' 2>&1 ; echo exitstatus:$?');
+		
+		my $ssh_wait_status;
+		eval {
+			$ssh_wait_status = $ssh->waitfor('exitstatus:[0-9]+', $timeout_seconds);
+		};
+		
+		if ($EVAL_ERROR) {
+			if ($EVAL_ERROR =~ /^(\w+) at \//) {
+				notify($ERRORS{'DEBUG'}, 0, $attempt_string . "$1 error occurred executing command on $computer_name: '$command'");
+			}
+			else {
+				notify($ERRORS{'DEBUG'}, 0, $attempt_string . "error occurred executing command on $computer_name: '$command'\nerror: $EVAL_ERROR");
+			}
+			next ATTEMPT;
+		}
+		elsif (!$ssh_wait_status) {
+			notify($ERRORS{'DEBUG'}, 0, $attempt_string . "command timed out after $timeout_seconds seconds on $computer_name: '$command'");
+			next ATTEMPT;
+		}
+		
+		my $output = $ssh->before() || '';
+		$output =~ s/(^\s+)|(\s+$)//g;
+		
+		my $exit_status_string = $ssh->match() || '';
+		my ($exit_status) = $exit_status_string =~ /(\d)+/;
+		if (!$exit_status_string || !defined($exit_status)) {
+			my $all_output = $ssh->read_all() || '';
+			notify($ERRORS{'WARNING'}, 0, $attempt_string . "failed to determine exit status from string: '$exit_status_string', output:\n$all_output");
+			next ATTEMPT;
+		}
+		
+		my @output_lines = split(/[\r\n]+/, $output);
+		
+		notify($ERRORS{'OK'}, 0, "executed command on $computer_name: '$command', exit status: $exit_status, output:\n$output") if ($display_output);
+		
+		# Save the SSH object for later use
+		$ENV{net_ssh_expect}{$computer_name} = $ssh;
+		
+		return ($exit_status, \@output_lines);
+	}
+	
+	notify($ERRORS{'WARNING'}, 0, $attempt_string . "failed to execute command on $computer_name: '$command'") if ($display_output);
+	return;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 =head2 get_os_type
 
  Parameters  : None
@@ -1661,43 +1835,51 @@ sub get_os_type {
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 manage_server_access
+
+ Parameters  : None
+ Returns     : 
+ Description : 
+
+=cut
+
 sub manage_server_access {
 
 	my $self = shift;
-        if (ref($self) !~ /VCL::Module/i) {
-                notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
-                return;
-        }
-
-        my $computer_node_name = $self->data->get_computer_node_name() || return;
-	my $reservation_id        = $self->data->get_reservation_id();
-	my $server_request_id     = $self->data->get_server_request_id();
-        my $server_request_admingroupid = $self->data->get_server_request_admingroupid();
-        my $server_request_logingroupid = $self->data->get_server_request_logingroupid();
-
-	#Build list of users.
-	#If in admin group set admin flag
-	#If in both login and admin group, only use admin setting
-	#Check if user is in reserverationaccounts table, add user if needed
-	#Check if user exists on server, add if needed
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+
+	my $computer_node_name          = $self->data->get_computer_node_name() || return;
+	my $reservation_id              = $self->data->get_reservation_id();
+	my $server_request_id           = $self->data->get_server_request_id();
+	my $server_request_admingroupid = $self->data->get_server_request_admingroupid();
+	my $server_request_logingroupid = $self->data->get_server_request_logingroupid();
+
+	# Build list of users.
+	# If in admin group set admin flag
+	# If in both login and admin group, only use admin setting
+	# Check if user is in reserverationaccounts table, add user if needed
+	# Check if user exists on server, add if needed
 	
 	my @userlist_admin;
 	my @userlist_login;
 	my %user_hash;
 	my $ssh_allow_list;
 
-	if ( $server_request_admingroupid ) {
+	if ($server_request_admingroupid) {
 		@userlist_admin = getusergroupmembers($server_request_admingroupid);
 	}
-	if ( $server_request_logingroupid ) {
+	if ($server_request_logingroupid) {
 		@userlist_login = getusergroupmembers($server_request_logingroupid);
 	}	
 	
 	notify($ERRORS{'OK'}, 0, " admin list= @userlist_admin");
 	notify($ERRORS{'OK'}, 0, " login list= @userlist_login");
-
 	
-	if ( scalar @userlist_admin > 0 ) {
+	
+	if (scalar @userlist_admin > 0) {
 		foreach my $str (@userlist_admin) {
 			my ($username,$uid,$vcl_user_id) = split(/:/, $str);
 			$user_hash{$uid}{"username"} = $username;
@@ -1707,7 +1889,7 @@ sub manage_server_access {
 			notify($ERRORS{'OK'}, 0, "adding admin $uid for $username ");
 		}
 	}		
-	if ( scalar @userlist_login > 0 ) {
+	if (scalar @userlist_login > 0) {
 		foreach my $str (@userlist_login) {
 			notify($ERRORS{'OK'}, 0, "admin str= $str");
 			my ($username, $uid,$vcl_user_id) = split(/:/, $str);
@@ -1724,18 +1906,18 @@ sub manage_server_access {
 		}
 	}	
 
-	#Collect users in reservationaccounts table
+	# Collect users in reservationaccounts table
 	my %res_accounts = get_reservation_accounts($reservation_id);
 	my $not_standalone_list = "";
 	my $standalone = 0;
 	if(defined($ENV{management_node_info}{NOT_STANDALONE}) && $ENV{management_node_info}{NOT_STANDALONE}){
-                $not_standalone_list = $ENV{management_node_info}{NOT_STANDALONE};
-        }
+		$not_standalone_list = $ENV{management_node_info}{NOT_STANDALONE};
+	}
 
 	foreach my $userid (sort keys %user_hash) {
 		next if (!($userid));
 		if(!exists($res_accounts{$userid})){
-			#check affiliation
+			# check affiliation
 			notify($ERRORS{'OK'}, 0, "checking affiliation for $userid");
 			my $affiliation_name = get_user_affiliation($user_hash{$userid}{vcl_user_id}); 
 			if(defined($affiliation_name)) {
@@ -1744,7 +1926,7 @@ sub manage_server_access {
 					$standalone = 1;
 				}
 			}
-			#IF standalone - generate password
+			# IF standalone - generate password
 			if($standalone) {
 				$user_hash{$userid}{"passwd"} = getpw();
 			}
@@ -1792,10 +1974,9 @@ sub manage_server_access {
 
 =head2 process_connect_methods
 
- Parameters  : None
- Returns     : If successful: 1
-               If failed: 0
- Description : starts and open port for available connection methods
+ Parameters  : none
+ Returns     : boolean
+ Description : Processes the connect methods configured for the image revision.
 
 =cut
 
@@ -1806,84 +1987,111 @@ sub process_connect_methods {
 		return;
 	}
 	
-	my $mode = shift;
-	if (!$mode) {
-		notify($ERRORS{'OK'}, 0, "Mode variable not passed in as an argument");
-		return 0;
-	}
-	
-	my $computer_node_name   = $self->data->get_computer_node_name();
-	my $connect_methods	 = $self->data->get_connect_methods();
+	my $computer_node_name = $self->data->get_computer_node_name();
+	my $imagerevision_id   = $self->data->get_imagerevision_id();
 	
-	foreach my $CMid (sort keys %{$connect_methods} ) {
-		notify($ERRORS{'OK'}, 0, "id= $$connect_methods{$CMid}{id}") if(defined ($$connect_methods{$CMid}{id}) );
-		notify($ERRORS{'OK'}, 0, "description= $$connect_methods{$CMid}{description}") if(defined ($$connect_methods{$CMid}{description}) );
-		notify($ERRORS{'OK'}, 0, "port== $$connect_methods{$CMid}{port}") if(defined ($$connect_methods{$CMid}{port}) );
-		notify($ERRORS{'OK'}, 0, "servicename= $$connect_methods{$CMid}{servicename}") if(defined ($$connect_methods{$CMid}{servicename}) );
-		notify($ERRORS{'OK'}, 0, "startupscript= $$connect_methods{$CMid}{startupscript}") if(defined ($$connect_methods{$CMid}{startupscript}) );
-		notify($ERRORS{'OK'}, 0, "autoprov= $$connect_methods{$CMid}{autoprovisioned}") if(defined ($$connect_methods{$CMid}{autoprovisioned}) );
-		my $description = $$connect_methods{$CMid}{description};
-		my $port = $$connect_methods{$CMid}{port};
-		
-		my $service_started = 0;	
-		notify($ERRORS{'OK'}, 0, "checking if servicename exists ");
-		if( defined ($$connect_methods{$CMid}{servicename}) && $$connect_methods{$CMid}{servicename} ) {
-			# does service exist
-			my $servicename = $$connect_methods{$CMid}{servicename};
-			notify($ERRORS{'OK'}, 0, "trying to start servicename $servicename ");
-			if( $self->can("service_exists")) {
-				if($self->service_exists($servicename)) {
-					if( $self->can("start_service")) {
-						if( $self->start_service($servicename) ) {
-							notify($ERRORS{'OK'}, 0, "Service $servicename started");
-							$service_started = 1;
-						}
-						else {
-							notify($ERRORS{'WARNING'}, 0, "Service $servicename failed to start");
-						}
+	# Retrieve the connect method info hash
+	my $connect_method_info = get_connect_method_info($imagerevision_id);
+	if (!$connect_method_info) {
+		notify($ERRORS{'WARNING'}, 0, "no connect methods are configured for image revision $imagerevision_id");
+	}
+	
+	my $remote_ip = $self->data->get_reservation_remote_ip();
+	if (!$remote_ip) {
+		notify($ERRORS{'WARNING'}, 0, "reservation remote IP address is not defined, connect methods will be available from any IP address");
+		$remote_ip = '0.0.0.0/0.0.0.0';
+	}
+	
+	CONNECT_METHOD: for my $connect_method_id (sort keys %{$connect_method_info} ) {
+		notify($ERRORS{'DEBUG'}, 0, "processing connect method:\n" . format_data($connect_method_info->{$connect_method_id}));
+		
+		my $name            = $connect_method_info->{$connect_method_id}{name};
+		my $description     = $connect_method_info->{$connect_method_id}{description};
+		my $protocol        = $connect_method_info->{$connect_method_id}{protocol} || 'TCP';
+		my $port            = $connect_method_info->{$connect_method_id}{port};
+		my $service_name    = $connect_method_info->{$connect_method_id}{servicename};
+		my $startup_script  = $connect_method_info->{$connect_method_id}{startupscript};
+		my $install_script  = $connect_method_info->{$connect_method_id}{installscript};
+		my $disabled        = $connect_method_info->{$connect_method_id}{connectmethodmap}{disabled};
+		
+		if ($disabled) {
+			# TODO: Add code to disable the service, close the firewall port, etc
+			notify($ERRORS{'OK'}, 0, "skipping '$name' connect method configuration, it should be disabled on $computer_node_name");
+		}
+		else {
+			# Attempt to start and configure the connect method
+			my $service_started = 0;
+			
+			# Attempt to start the service if the service name has been defined for the connect method
+			if ($service_name) {
+				# Check if service exists
+				notify($ERRORS{'DEBUG'}, 0, "checking if '$service_name' service exists for '$name' connect method on $computer_node_name");
+				if ($self->service_exists($service_name)) {
+					notify($ERRORS{'DEBUG'}, 0, "attempting to start '$service_name' service for '$name' connect method on $computer_node_name");
+					if ($self->start_service($service_name)) {
+						notify($ERRORS{'OK'}, 0, "'$service_name' started on $computer_node_name");
+						$service_started = 1;
+					}
+					else {
+						notify($ERRORS{'WARNING'}, 0, "failed to start '$service_name' service for '$name' connect method on $computer_node_name");
 					}
 				}
+				#elsif ($install_script) {
+				#	notify($ERRORS{'DEBUG'}, 0, "'$service_name' service for '$name' connect method does not exist on $computer_node_name, attempting to execute connect method install script");
+				#	my ($install_exit_status, $install_output) = $self->execute($install_script, 1, 600, 1);
+				#	if (!defined($install_output)) {
+				#		notify($ERRORS{'WARNING'}, 0, "failed to run command to execute install script for '$name' connect method on $computer_node_name, command: '$install_script'");
+				#	}
+				#	else {
+				#		notify($ERRORS{'OK'}, 0, "executed install script for '$name' connect method on $computer_node_name, command: '$install_script', exit status: $install_exit_status, output:\n" . join("\n", @$install_output));
+				#		
+				#		if ($self->service_exists($service_name)) {
+				#			if ($self->start_service($service_name)) {
+				#				notify($ERRORS{'OK'}, 0, "'$service_name' started on $computer_node_name");
+				#				$service_started = 1;
+				#			}
+				#			else {
+				#				notify($ERRORS{'WARNING'}, 0, "failed to start '$service_name' service after executing install script for '$name' connect method on $computer_node_name");
+				#			}
+				#		}
+				#		else {
+				#			notify($ERRORS{'WARNING'}, 0, "'$service_name' service does NOT exist after executing install script for '$name' connect method on $computer_node_name");
+				#		}
+				#	}
+				#}
 				else {
-					notify($ERRORS{'WARNING'}, 0, "Service $servicename does not exist on $computer_node_name");
+					notify($ERRORS{'WARNING'}, 0, "'$service_name' service for '$name' connect method does NOT exist on $computer_node_name, connect method install script is not defined");
 				}
 			}
-			else {
-				notify($ERRORS{'WARNING'}, 0, "service_exists not implemented by OS module" . ref($self));
-			}
-		}
-		
-		if ( !$service_started && defined ($$connect_methods{$CMid}{startupscript} ) ) {
-			notify($ERRORS{'OK'}, 0, "startupscript exists and service NOT started ");
 			
-			#Service command did not work or does not exist
-			# Try to use startup script
-			my $cmd = $$connect_methods{$CMid}{startupscript} . " start";
-			notify($ERRORS{'OK'}, 0, "service not started, attempt to run $cmd ");
-			if( $self->can("execute") ) {
-				if( $self->execute($cmd, 1) ){
-					$service_started = 1;	
-				}	
-			}
-			else {
-				notify($ERRORS{'OK'}, 0, "execute routing not available by module" . ref($self) );	
+			# Run the startup script if the service is not started
+			if (!$service_started && defined($startup_script)) {
+				notify($ERRORS{'DEBUG'}, 0, "attempting to run startup script '$startup_script' for '$name' connect method on $computer_node_name");
+				my ($startup_exit_status, $startup_output) = $self->execute($startup_script, 1);
+				if (!defined($startup_output)) {
+					notify($ERRORS{'WARNING'}, 0, "failed to run command to execute startup script '$startup_script' for '$name' connect method on $computer_node_name, command: '$startup_script'");
+				}
+				elsif ($startup_exit_status == 0){
+					notify($ERRORS{'OK'}, 0, "executed startup script '$startup_script' for '$name' connect method on $computer_node_name, command: '$startup_script', exit status: $startup_exit_status, output:\n" . join("\n", @$startup_output));	
+				}
+				else {
+					notify($ERRORS{'WARNING'}, 0, "failed to execute startup script '$startup_script' for '$name' connect method on $computer_node_name, command: '$startup_script', exit status: $startup_exit_status, output:\n" . join("\n", @$startup_output));
+				}
 			}
-		}	
-		
-		if ( $service_started ) {
-			#open firewall port
-			notify($ERRORS{'OK'}, 0, "service started ");
-			if($self->can("enable_firewall_port")) {
-				notify($ERRORS{'OK'}, 0, "trying to enable firewall port $port on $computer_node_name ");
-				if(!$self->enable_firewall_port($port)) {
-					notify($ERRORS{'CRITICAL'}, 0, "Failed to enable firewall Connect Method $CMid $description on $computer_node_name");
+			
+			# Open the firewall port
+			if (defined($port)) {
+				notify($ERRORS{'DEBUG'}, 0, "attempting to open firewall port $port on $computer_node_name for '$name' connect method");
+				if ($self->enable_firewall_port($protocol, $port, "$remote_ip/24", 1)) {
+					notify($ERRORS{'OK'}, 0, "opened firewall port $port on $computer_node_name for '$name' connect method");
+				}
+				else {
+					notify($ERRORS{'WARNING'}, 0, "failed to open firewall port $port on $computer_node_name for '$name' connect method");
 				}
 			}
 		}
-		else {
-			notify($ERRORS{'WARNING'}, 0, "Connect Method $CMid $description failed to start on $computer_node_name");
-		}
 	}
-	
+
 	return 1;	
 }
 
@@ -1901,10 +2109,10 @@ sub process_connect_methods {
 sub is_user_connected {
 	
 	my $self = shift;
-        if (ref($self) !~ /VCL::Module/i) {
-                notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
-                return;
-        }
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
 	my $time_limit = shift;
 	if ( !$time_limit ) {
 		notify($ERRORS{'WARNING'}, 0, "time_limit variable not passed as an argument");
@@ -1912,7 +2120,7 @@ sub is_user_connected {
 	}
 
 	my $request_id           = $self->data->get_request_id();	
-        my $computer_node_name   = $self->data->get_computer_node_name();
+	my $computer_node_name   = $self->data->get_computer_node_name();
 	my $request_state_name	 = $self->data->get_request_state_name();
 	my $user_unityid         = $self->data->get_user_login_id();
 	my $computer_ip_address  = $self->data->get_computer_ip_address();
@@ -1920,41 +2128,41 @@ sub is_user_connected {
 	my $connect_methods      = $self->data->get_connect_methods();	
 	
 	my $start_time    = time();
-        my $time_exceeded = 0;
-        my $break         = 0;
-        my $ret_val       = "no";
-
-        # Figure out number of loops for log messages
-        my $maximum_loops = $time_limit * 2;
-        my $loop_count    = 0;
+	my $time_exceeded = 0;
+	my $break         = 0;
+	my $ret_val       = "no";
+
+	# Figure out number of loops for log messages
+	my $maximum_loops = $time_limit * 2;
+	my $loop_count    = 0;
 	
  	while (!$break) {
-                $loop_count++;
+		$loop_count++;
 
-                notify($ERRORS{'OK'}, 0, "checking for connection by $user_unityid on $computer_node_name, attempt $loop_count ");
+		notify($ERRORS{'OK'}, 0, "checking for connection by $user_unityid on $computer_node_name, attempt $loop_count ");
 
-                if (is_request_deleted($request_id)) {
-                        notify($ERRORS{'OK'}, 0, "user has deleted request");
-                        $break   = 1;
-                        $ret_val = "deleted";
-                        return $ret_val;
-                }
+		if (is_request_deleted($request_id)) {
+			notify($ERRORS{'OK'}, 0, "user has deleted request");
+			$break   = 1;
+			$ret_val = "deleted";
+			return $ret_val;
+		}
 		
 		$time_exceeded = time_exceeded($start_time, $time_limit);
-                if ($time_exceeded) {
-                        notify($ERRORS{'OK'}, 0, "$time_limit minute time limit exceeded begin cleanup process");
-                        #time_exceeded, begin cleanup process
-                        $break = 1;
-                        if ($request_state_name =~ /reserved/) {
-                                notify($ERRORS{'OK'}, 0, "user never logged in returning nologin");
-                                $ret_val = "nologin";
-                        }
-                        else {
-                                $ret_val = "timeout";
-                        }
-                        return $ret_val;
-                } ## end if ($time_exceeded)
-		else {    #time not exceeded check for connection
+		if ($time_exceeded) {
+			notify($ERRORS{'OK'}, 0, "$time_limit minute time limit exceeded begin cleanup process");
+			# time_exceeded, begin cleanup process
+			$break = 1;
+			if ($request_state_name =~ /reserved/) {
+				notify($ERRORS{'OK'}, 0, "user never logged in returning nologin");
+				$ret_val = "nologin";
+			}
+			else {
+				$ret_val = "timeout";
+			}
+			return $ret_val;
+		} ## end if ($time_exceeded)
+		else {    # time not exceeded check for connection
 			foreach my $CMid (sort keys %{$connect_methods} ) {
 				if($self->can("check_connection_on_port")) {
 					if(defined($$connect_methods{$CMid}{port}) && $$connect_methods{$CMid}{port}) {
@@ -1974,8 +2182,8 @@ sub is_user_connected {
 			}
 		}
 		notify($ERRORS{'DEBUG'}, 0, "sleeping for 20 seconds");
-                sleep 20;
-	}	
+		sleep 20;
+	}
 	return $ret_val;
 }
 

Modified: incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm?rev=1179363&r1=1179362&r2=1179363&view=diff
==============================================================================
--- incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm (original)
+++ incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm Wed Oct  5 18:22:54 2011
@@ -3044,41 +3044,42 @@ EOF
 =cut
 
 sub service_exists {
-        my $self = shift;
-        if (ref($self) !~ /linux/i) {
-                notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
-                return;
-        }
-
-        my $management_node_keys = $self->data->get_management_node_keys();
-        my $computer_node_name   = $self->data->get_computer_node_name();
-
-        my $service_name = shift;
-        if (!$service_name) {
-                notify($ERRORS{'WARNING'}, 0, "service name was not passed as an argument");
-                return;
-        }
-
+	my $self = shift;
+	if (ref($self) !~ /linux/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	my $management_node_keys = $self->data->get_management_node_keys();
+	my $computer_node_name   = $self->data->get_computer_node_name();
+	
+	my $service_name = shift;
+	if (!$service_name) {
+		notify($ERRORS{'WARNING'}, 0, "service name was not passed as an argument");
+		return;
+	}
+	
 	my $command = "/sbin/chkconfig --list $service_name";
-	my ($status, $output) = run_ssh_command($computer_node_name, $management_node_keys, $command, '', '', 1);
-	if (defined($output) && grep(/error reading information on service/i, @{$output})) {
-                notify($ERRORS{'DEBUG'}, 0, "service does not exist: $service_name");
-                return 0;
-        }
-        elsif (defined($status) && $status == 0) {
-                notify($ERRORS{'DEBUG'}, 0, "service exists: $service_name");
-        }
-        elsif (defined($status)) {
-                notify($ERRORS{'WARNING'}, 0, "unable to determine if service exists: $service_name, exit status: $status, output:\n@{$output}");
-                return;
-        }
-        else {
-                notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to determine if service exists");
-                return;
-        }
-
-        return 1;
+	my ($exit_status, $output) = run_ssh_command($computer_node_name, $management_node_keys, $command, '', '', 1);
+	if (!defined($output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute command to determine if '$service_name' service exists on $computer_node_name");
+		return;
+	}
+	elsif (grep(/error reading information on service/i, @$output)) {
+		notify($ERRORS{'DEBUG'}, 0, "'$service_name' service does not exist on $computer_node_name");
+		return 0;
+	}
+	elsif ($exit_status == 0 || grep(/not referenced in any runlevel/i, @$output)) {
+		# chkconfig may display the following if the service exists but has not been added:
+		# service ext_sshd supports chkconfig, but is not referenced in any runlevel (run 'chkconfig --add ext_sshd')
+		notify($ERRORS{'DEBUG'}, 0, "'$service_name' service exists but is not referenced in any runlevel");
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine if '$service_name' service exists, exit status: $exit_status, output:\n" . join("\n", @$output));
+		return;
+	}
 	
+	return 1;
 }
 
 #/////////////////////////////////////////////////////////////////////////////
@@ -3381,9 +3382,9 @@ sub get_total_memory {
 
 =head2 enable_firewall_port
  
-  Parameters  : none
-  Returns     : 1 successful, 0 failed
-  Description : updates iptables for given port for collect IPaddress range and mode
+  Parameters  : $protocol, $port, $scope (optional), $overwrite_existing (optional), $name (optional), $description (optional)
+  Returns     : boolean
+  Description : Updates iptables for given port for collect IPaddress range and mode
  
 =cut
 
@@ -3394,56 +3395,42 @@ sub enable_firewall_port {
       return;
    }
 	
-	my $port = shift;
-	if(!$port) {
-		notify($ERRORS{'CRITICAL'}, 0, "Input variable port was not passed in as an argument");
-		return 0;
-	} 
-	
-	my $mode = shift;
-	if(!$mode) {
-		notify($ERRORS{'DEBUG'}, 0, "firewall mode not passed in as an argument setting to loose");
-		$mode = "medium";
+	my ($protocol, $port, $scope_argument, $overwrite_existing, $name, $description) = @_;
+	if (!defined($protocol) || !defined($port)) {
+		notify($ERRORS{'WARNING'}, 0, "protocol and port arguments were not supplied");
+		return;
 	}
 	
    my $computer_node_name = $self->data->get_computer_node_name();
-	my $remote_ip = $self->data->get_reservation_remote_ip();
 	
-	my $scope;
-	my $command;
-		
-	if ( $mode =~ /loose/i ) {
-		$command = "/sbin/iptables -I INPUT 1 -m state --state NEW,RELATED,ESTABLISHED -m tcp -p tcp --dport $port -j ACCEPT";
-	}	 
-	elsif($mode =~ /medium/i) {	
-		$scope = "$remote_ip/16";
-		$command = "/sbin/iptables -I INPUT 1 -s $scope -m state --state NEW,RELATED,ESTABLISHED -m tcp -p tcp --dport $port -j ACCEPT";
-	}
-	elsif( $mode =~ /tight/i) {
-		$scope = "$remote_ip/24";
-		$command = "/sbin/iptables -I INPUT 1 -s $scope -m state --state NEW,RELATED,ESTABLISHED -m tcp -p tcp --dport $port -j ACCEPT";
-	}
-	elsif( $mode =~ /locked/i) {
-		$command = "/sbin/iptables -I INPUT 1 -s $remote_ip -m state --state NEW,RELATED,ESTABLISHED -m tcp -p tcp --dport $port -j ACCEPT";
-	}
-
-	#copy original config
-	my $cp_iptables_config = "cp /etc/sysconfig/iptables /etc/sysconfig/iptables_pre_$port";
-	my ($status_cp, $output_cp) = $self->execute($cp_iptables_config);	
-	if (defined $status_cp && $status_cp == 0) {
-		notify($ERRORS{'DEBUG'}, 0, "executed command $cp_iptables_config on $computer_node_name");
+	$protocol = lc($protocol);
+	
+	my $command = "/sbin/iptables -I INPUT 1 -m state --state NEW,RELATED,ESTABLISHED -m $protocol -p $protocol --dport $port -j ACCEPT";
+	
+	if ($scope_argument) {
+		$command .= " -s $scope_argument";
+	}
+
+	# Make backup copy of original iptables configuration
+	my $iptables_backup_file_path = "/etc/sysconfig/iptables_pre_$port";
+	if ($self->copy_file("/etc/sysconfig/iptables", $iptables_backup_file_path)) {
+		notify($ERRORS{'DEBUG'}, 0, "backed up original iptables file to: '$iptables_backup_file_path'");
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "failed to back up original iptables file to: '$iptables_backup_file_path'");
 	}
 	
 	# Add rule
+	notify($ERRORS{'DEBUG'}, 0, "attempting to execute command on $computer_node_name: '$command'");
 	my ($status, $output) = $self->execute($command);	
 	if (defined $status && $status == 0) {
-		notify($ERRORS{'DEBUG'}, 0, "executed command $command on $computer_node_name");
+		notify($ERRORS{'DEBUG'}, 0, "executed command on $computer_node_name: '$command'");
 	}
 	else {
-		notify($ERRORS{'WARNING'}, 0, "output from iptables:" . join("\n", @$output));
+		notify($ERRORS{'WARNING'}, 0, "output from iptables:\n" . join("\n", @$output));
 	}
 	
-	#Save rules to sysconfig/iptables -- incase of reboot
+	# Save rules to sysconfig/iptables -- incase of reboot
 	my $iptables_save_cmd = "/sbin/iptables-save > /etc/sysconfig/iptables";
 	my ($status_save, $output_save) = $self->execute($iptables_save_cmd);	
 	if (defined $status_save && $status_save == 0) {

Modified: incubator/vcl/trunk/managementnode/lib/VCL/utils.pm
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/utils.pm?rev=1179363&r1=1179362&r2=1179363&view=diff
==============================================================================
--- incubator/vcl/trunk/managementnode/lib/VCL/utils.pm (original)
+++ incubator/vcl/trunk/managementnode/lib/VCL/utils.pm Wed Oct  5 18:22:54 2011
@@ -117,6 +117,7 @@ our @EXPORT = qw(
   get_computer_ids
   get_computer_info
   get_computers_controlled_by_MN
+  get_connect_method_info
   get_current_file_name
   get_current_package_name
   get_current_subroutine_name
@@ -4645,8 +4646,8 @@ sub get_request_info {
 		my $computer_info = get_computer_info($computer_id, 1);
 		$request_info{reservation}{$reservation_id}{computer} = $computer_info;
 
-		my $connect_methods = get_connect_methods($image_id, $imagerevision_id);
-		$request_info{reservation}{$reservation_id}{connect_methods} = $connect_methods;
+		my $connect_method_info = get_connect_method_info($imagerevision_id);
+		$request_info{reservation}{$reservation_id}{connect_methods} = $connect_method_info;
 		
 	}    # Close loop through selected rows
 	
@@ -4797,7 +4798,7 @@ sub get_request_info {
 		$request_info{reservation}{$reservation_id}{computer}{SHORTNAME} = $computer_shortname;
 
 		# Add the managementnode info to the hash
-		my $management_node_id   = $request_info{reservation}{$reservation_id}{managementnodeid};
+		my $management_node_id = $request_info{reservation}{$reservation_id}{managementnodeid};
 		my $management_node_info = get_management_node_info($management_node_id);
 		if (!$management_node_info) {
 			notify($ERRORS{'WARNING'}, 0, "failed to retrieve management node info");
@@ -5061,6 +5062,7 @@ sub get_image_info {
 		'image',
 		'platform',
 		'OS',
+		'OStype',
 		'module',
 	);
 	
@@ -5084,12 +5086,14 @@ FROM
 image,
 platform,
 OS,
+OStype,
 module
 
 WHERE
 platform.id = image.platformid
 AND OS.id = image.OSid
 AND module.id = OS.moduleid
+AND OS.type = OStype.name
 AND 
 EOF
 	
@@ -5132,7 +5136,7 @@ EOF
 		if ($table eq $tables[0]) {
 			$image_info->{$column} = $value;
 		}
-		elsif ($table eq 'module') {
+		elsif ($table =~ /^(module|OStype)$/) {
 			$image_info->{OS}{$table}{$column} = $value;
 		}
 		else {
@@ -5586,6 +5590,8 @@ sub run_ssh_command {
 	$timeout_seconds = 0 if (!$timeout_seconds);
 	$identity_paths = $ENV{management_node_info}{keys} if (!defined $identity_paths || length($identity_paths) == 0);
 	
+#return VCL::Module::OS::execute($node, $command, $output_level, $timeout_seconds, $max_attempts, $port, $user);
+	
 	# TODO: Add ssh path to config file and set global variable
 	# Locate the path to the ssh binary
 	my $ssh_path;
@@ -9564,8 +9570,7 @@ sub xmlrpc_call {
 			"password: $XMLRPC_PASS\n" .
 			"arguments: " . join(", ", @arguments) . "\n" .
 			"fault code: " . $response->code . "\n" .
-			"fault string: " . $response->string .
-			format_data($response)
+			"fault string: " . $response->string
 		);
 		$ENV{rpc_xml_error} = $response->string;
 		return;
@@ -10667,83 +10672,119 @@ sub kill_child_processes {
 
 #/////////////////////////////////////////////////////////////////////////////
 
-=head2 get_connect_method
+=head2 get_connect_method_info
 
- Parameters  : reservationid
+ Parameters  : $imagerevision_id
  Returns     : hash reference
- Description : Returns the contents of connect method for reservationid.
+ Description : Returns the connect methods for the image revision specified as
+               the argument.
 
 =cut
 
-sub get_connect_methods {
+sub get_connect_method_info {
+	my ($imagerevision_id) = @_;
+	if (!defined($imagerevision_id)) {
+		notify($ERRORS{'WARNING'}, 0, "imagerevision ID argument was not supplied");
+		return;
+	}
+	
+	my $imagerevision_info = get_imagerevision_info($imagerevision_id);
+	
+	notify($ERRORS{'DEBUG'}, 0, "attempting to retrieve connect method info:\n" .
+		"imagerevision: $imagerevision_id - " . $imagerevision_info->{imagename} . "\n" .
+		"OS: " . $imagerevision_info->{image}{OS}{id} . " - " . $imagerevision_info->{image}{OS}{name} . "\n" .
+		"OS type: " . $imagerevision_info->{image}{OS}{OStype}{id} . " - " . $imagerevision_info->{image}{OS}{OStype}{name}
+	);
+	
+	# Get a hash ref containing the database column names
+	my $database_table_columns = get_database_table_columns();
+	
+	my @tables = (
+		'connectmethod',
+		'connectmethodmap'
+	);
+	
+	# Construct the select statement
+	my $select_statement = "SELECT DISTINCT\n";
+	
+	# Get the column names for each table and add them to the select statement
+	for my $table (@tables) {
+		my @columns = @{$database_table_columns->{$table}};
+		for my $column (@columns) {
+			$select_statement .= "$table.$column AS '$table-$column',\n";
+		}
+	}
+	
+	# Remove the comma after the last column line
+	$select_statement =~ s/,$//;
+	
+	# Complete the select statement
+	$select_statement .= <<EOF;
+FROM
+connectmethod,
+connectmethodmap,
+imagerevision
 
-        my ($imageid, $imagerevisionid) = @_;
-        my ($calling_package, $calling_filename, $calling_line, $calling_sub) = caller(0);
+LEFT JOIN image ON (image.id = imagerevision.imageid)
+LEFT JOIN OS ON (OS.id = image.OSid)
+LEFT JOIN OStype ON (OStype.name = OS.type)
 
-        if (!defined($imagerevisionid)) {
-                notify($ERRORS{'WARNING'}, 0, "$calling_sub $calling_package missing mandatory variable: imagerevisionid ");
-                return 0;
-        }
-        
-        if (!defined($imageid)) {
-                notify($ERRORS{'WARNING'}, 0, "$calling_sub $calling_package missing mandatory variable: imageid ");
-                return 0;
-        }
-
-        my $select_statement = "
-        SELECT DISTINCT
-        c.id AS CM_id,
-        c.description AS CM_description,
-        c.port AS CM_port,
-        c.servicename AS CM_servicename,
-        c.startupscript AS CM_startupscript,
-        cm.autoprovisioned AS CM_autoprovisioned,
-        cm.disabled AS CM_disabled
-        FROM
-        connectmethod c,
-        connectmethodmap cm,
-        image i
-        LEFT JOIN OS o ON (o.id = i.OSid)
-        LEFT JOIN OStype ot ON (ot.name = o.type)
-        WHERE 
-        i.id = $imageid AND
-        cm.connectmethodid = c.id AND
-        cm.autoprovisioned IS NULL AND
-        (cm.OStypeid = ot.id OR
-        cm.OSid = o.id OR 
-        cm.imagerevisionid = $imagerevisionid)
-        ORDER BY cm.disabled, c.description";
+WHERE
+connectmethodmap.connectmethodid = connectmethod.id
+AND imagerevision.id = $imagerevision_id
+AND connectmethodmap.autoprovisioned IS NOT NULL
+AND (
+	connectmethodmap.OStypeid = OStype.id
+	OR connectmethodmap.OSid = OS.id 
+	OR connectmethodmap.imagerevisionid = imagerevision.id
+)
 
-        # Call the database select subroutine
-        # This will return an array of one or more rows based on the select statement
-        my @selected_rows = database_select($select_statement);
+ORDER BY
+connectmethod.id,
+connectmethodmap.imagerevisionid,
+connectmethodmap.OSid,
+connectmethodmap.OStypeid,
+connectmethodmap.disabled
+EOF
 
-        my @ret_array;
-        my %connect_info;
+	# Call the database select subroutine
+	my @selected_rows = database_select($select_statement);
 	
-	        # Check to make sure 1 or more rows were returned
-        if (scalar @selected_rows > 0) {
-                # It contains a hash
-                for (@selected_rows) {
-                        my %connectMethod = %{$_};
-                        next if($connectMethod{CM_disabled});
-                        my $CMid = $connectMethod{CM_id};
-                        $connect_info{$CMid}{"id"} = $CMid;
-                        $connect_info{$CMid}{"description"} = $connectMethod{CM_description};
-                        $connect_info{$CMid}{"port"} = $connectMethod{CM_port};
-                        $connect_info{$CMid}{"servicename"} = $connectMethod{CM_servicename};
-                        $connect_info{$CMid}{"startupscript"} = $connectMethod{CM_startupscript};
-                        $connect_info{$CMid}{"autoprovisioned"} = $connectMethod{CM_autoprovisioned};
+	# Transform the array of database rows into a hash
+	my $connect_method_info = {};
+	
+	for my $row (@selected_rows) {	
+		notify($ERRORS{'DEBUG'}, 0, $row->{"connectmethod-name"} . ": " .
+		"connectmethodid=" . $row->{"connectmethod-id"} . ", " .
+		"OStypeid=" . ($row->{"connectmethodmap-OStypeid"} || 'NULL') . ", " .
+		"OSid=" . ($row->{"connectmethodmap-OSid"} || 'NULL') . ", " .
+		"imagerevisionid=" . ($row->{"connectmethodmap-imagerevisionid"} || 'NULL') . ", " .
+		"disabled=" . $row->{"connectmethodmap-disabled"});
+		
+		my $connectmethod_id = $row->{'connectmethod-id'};
+		
+		# Loop through all the columns returned
+		for my $key (keys %$row) {
+			next if $key eq 'connectmethod-connecttext';
 			
-			notify($ERRORS{'OK'}, 0, "CONNECT METHOD CMid= $CMid description= $connect_info{$CMid}{description}");
-        
-                }
-                
-                return \%connect_info;
-        }       
-        
-        return();       
+			my $value = $row->{$key};
+			
+			# Split the table-column names
+			my ($table, $column) = $key =~ /^([^-]+)-(.+)/;
+			
+			# Add the values for the primary table to the hash
+			# Add values for other tables under separate keys
+			if ($table eq $tables[0]) {
+				$connect_method_info->{$connectmethod_id}{$column} = $value;
+			}
+			else {
+				$connect_method_info->{$connectmethod_id}{$table}{$column} = $value;
+			}
+		}
+	}
 
+	notify($ERRORS{'DEBUG'}, 0, "retrieved connect method info:\n" . format_data($connect_method_info));
+	return $connect_method_info;
 }
 
 #/////////////////////////////////////////////////////////////////////////////