Updated: June 18, 2009
Added a semicolon to run-esx-report.sh that was left out and responsible for some ugly HTML formatting.
With the secure SSH access problem solved in
Part 2, we'll move on to getting the data in the proper format and emailing it from the ESX Service Console. As you probably know, the Linux distribution installed with ESX 3.5 lacks
sendmail or an equivalent command, but we can roll our own from a perl script.
The perl mailer script
We need to import two perl modules for the script, and both are included by default in the Service Console.
Getopt::Std provides a simple way to get command line options, and
Net::SMTP will interface with an Exchange or SMTP server accessible from the console network
:
use Getopt::Std;
use Net::SMTP;
This getopt call is all that's necessary to declare the command line options (-f, -r, -s, etc.), and it will automatically populate a set of corresponding variables named opt_*. We'll do a quick check to make sure all the command line options were specified, and if not display the usage message:
getopt ('frsmb');
unless ($opt_f && $opt_r && $opt_s && $opt_m && $opt_b) {
print_usage();
exit 1;
}
Next we'll create a filehandle named BODY, opening the file specified on the command line. After reading in each line to the variable body_data, we'll close the handle:
open(BODY, $opt_b) || error("Could not open file $opt_b.");
my @body_data=<BODY>;
close(BODY);
The Net::SMTP module is pretty straightforward. To generate a HTML formatted email, we just need to specify the MIME version, the content type as HTML, and the character encoding as ISO 8859-1. If you would rather send the message as plain text, just remove those two lines:
my $smtp = Net::SMTP->new($opt_m) ||
error("SMTP connection to $opt_m failed.");
$smtp->mail($opt_f);
$smtp->to($opt_r);
$smtp->data();
$smtp->datasend("MIME-Version: 1.0\n");
$smtp->datasend("Content-Type: text/html; charset=iso-8859-1\n");
$smtp->datasend("To: $opt_r\n");
$smtp->datasend("From: $opt_f\n");
$smtp->datasend("Subject: $opt_s\n");
foreach $line (@body_data)
{
$smtp->datasend("$line");
}
$smtp->dataend();
$smtp->quit;
Here's the complete html-mailer.pl script:
###############################################################################
#
# html-mailer.pl
#
###############################################################################
#
# To create the html-mailer.pl script in the ~/esx-report directory, copy
# this entire code segment into your shell.
# If you'd rather copy just the script, select everything between the
# SCRIPTCREATOR limit strings.
#
###############################################################################
# If the ~/esx-report directory exists, cd to it so the script is created there
[ -d ~/esx-report ] && cd ~/esx-report
cat > ./html-mailer.pl <<'SCRIPTCREATOR'
#! /usr/bin/perl -w
use strict;
use Getopt::Std;
use Net::SMTP;
# Options:
# $opt_f email address of the sender
# $opt_r recipient email address
# $opt_s message subject, enclose in quotes if spaces
# $opt_m SMTP server FQDN or IP address
# $opt_b HTML formatted file for the message body
our ($opt_f, $opt_r, $opt_s, $opt_m, $opt_b);
getopt ('frsmb');
unless ($opt_f && $opt_r && $opt_s && $opt_m && $opt_b) {
print_usage();
exit 1;
}
open(BODY, $opt_b) || error("Unable to open file $opt_b");
my @body_data=<BODY>;
close(BODY);
my $line;
my $smtp = Net::SMTP->new($opt_m) ||
error("SMTP connection to $opt_m failed");
$smtp->mail($opt_f);
$smtp->to($opt_r);
$smtp->data();
$smtp->datasend("MIME-Version: 1.0\n");
$smtp->datasend("Content-Type: text/html; charset=iso-8859-1\n");
$smtp->datasend("To: $opt_r\n");
$smtp->datasend("From: $opt_f\n");
$smtp->datasend("Subject: $opt_s\n");
foreach $line (@body_data)
{
$smtp->datasend("$line");
}
$smtp->dataend();
$smtp->quit;
sub error {
my $msg = shift;
print STDERR "html-mailer.pl: $msg\n";
exit 1;
}
sub print_usage {
print STDERR <<EOF
html-mailer.pl - HTML Formatted Message Mailer
Usage: html-mailer.pl -f FROM -r RECIP -s SUBJ -m SMTP_HOST -b HTML_FILE
Sends an email to the specified address, filling the message body with the
HTML formatted file specified.
EOF
}
SCRIPTCREATOR
chmod 0700 ./html-mailer.pl
###############################################################################
|
Enable outbound SMTP
Now that we've got a script that we can send test messages with, we need to enable outbound SMTP through the ESX firewall on the ESX server that will have the script scheduled from a cron job. Just type this command as root to open the port:
# Execute as root
esxcfg-firewall --openPort 25,tcp,out,SMTP
|
If your Exchange or SMTP server is reachable from the Service Console network, execute html-mailer.pl with the appropriate parameters, and specify any old text file:
./html-mailer.pl -f me@mydomain.dom \
-r me@mydomain.dom \
-s "Test message" \
-m exchange.mydomain.dom \
-b ./testfile.txt
The Service Console can't reach the Exchange server...
No worries, as long as you're able to reach the VirtualCenter server, we can install the SMTP service and set it up to forward to the Exchange server. To install SMTP on a Windows 2003 server, do the following:
One script to rule them all
Almost there, so let's recap what we've done so far. In Part 1, we created the health check script that will run on each ESX server and send key performance stats and scaled histograms to the terminal. Then in Part 2, we covered how to distribute public keys so the script can be executed on several ESX servers via SSH. So far in Part 3, we've looked at a perl script that will email the combined script output, and now we need to create a script to tie it all together, and then schedule the script from a cron job.
Let's break down the main components of the script. First of all, if ssh-agent isn't running, the script isn't going to get very far, so we'll use pgrep to check for the process and exit if it's not found:
if ! pgrep ssh-agent >/dev/null; then
echo "The ssh-agent process does not appear to be running, exiting"
exit 1
fi
We need to source .ssh-agent, the file with the ssh-agent PID and socket info set up by the start-ssh-agent.sh script, or exit if it doesn't exist:
source "${HOME}/.ssh-agent" >/dev/null || exit 1
Since we'll be running everything from an ESX Service Console, and that server is likely to be part of the health check, we should compare the list of ESX hosts to the local hostname so we don't open a SSH connection to the local machine. We use cut here to strip off the domain name so we'll match whether the FQDN or just the bare hostname is specified:
THISHOST=$(hostname | cut -d . -f 1)
for host in $@; do
if [ $(echo $host | cut -d . -f 1) = $THISHOST ]; then
"${RUNDIR}/esx-report.sh" >> "$TEMPTEXT"
else
ssh -q $host "$(cat "${RUNDIR}/esx-report.sh")" >> "$TEMPTEXT" || \
printf "WARNING: SSH connection to $host failed\n\n\n\n" >> "$TEMPTEXT"
fi
done
After the health check script has looped through the list of ESX hosts, we'll start building the HTML file with the necessary tags. Setting the font size for the pre tag is the secret sauce for getting the email to display perfectly on a BlackBerry:
cat > "$TEMPHTML" <<-'HEADEREOF'
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style type="text/css">
body { font-family: monospace; font-size: 12px }
pre { font-family: monospace; font-size: 12px }
</style>
</head>
<body>
<pre>
HEADEREOF
If you need to use < or > symbols in a HTML document, you have to specify the actual ASCII code of the character, as HTML considers words wrapped in those symbols to be tags. We'll use a sed filter to replace all the >'s with the ASCII equivalent, and add a color tag to any lines with the word WARNING to make it stand out:
cat "$TEMPTEXT" | \
sed -e 's/>/\>/g' \
-e 's/WARNING:.*/<span style="color: red">&<\/span>/' >> "$TEMPHTML"
Then we'll add the closing tags for everything to the end of the HTML file:
cat >> "$TEMPHTML" <<-'FOOTEREOF'
</pre>
</body>
</html>
FOOTEREOF
And finally, we'll execute html-mailer.pl with the appropriate parameters. You'll need to change this section of the script for your environment:
"${RUNDIR}/html-mailer.pl" -f esx-report@lab.local \
-r administrator@lab.local \
-s "ESX Health Report" \
-m lab-vc \
-b "$TEMPHTML"
Here's the run-esx-report.sh script. Remember to change the email address and mail server parameters for your environment:
###############################################################################
#
# run-esx-report.sh
#
###############################################################################
#
# To create the run-esx-report.sh script in the ~/esx-report directory, copy
# this entire code segment into your shell.
# If you'd rather copy just the script, select everything between the
# SCRIPTCREATOR limit strings.
#
# putty will ignore all the tabs, making the copied script quite ugly
#
###############################################################################
# If the ~/esx-report directory exists, cd to it so the script is created there
[ -d ~/esx-report ] && cd ~/esx-report
cat > ./run-esx-report.sh <<'SCRIPTCREATOR'
#! /bin/bash
PATH="/bin:/usr/bin"
if [ -z $1 ]; then
echo "No ESX hosts specified, exiting"
exit 1
fi
if ! pgrep ssh-agent >/dev/null; then
echo "The ssh-agent process does not appear to be running, exiting"
exit 1
fi
RUNDIR=$(dirname "$(which "$0")")
source "${HOME}/.ssh-agent" >/dev/null || exit 1
THISHOST=$(hostname | cut -d . -f 1)
TEMPTEXT=$(mktemp "${RUNDIR}/temptext.XXXXXXXXXX")
TEMPHTML=$(mktemp "${RUNDIR}/temphtml.XXXXXXXXXX")
for host in $@; do
if [ $(echo $host | cut -d . -f 1) = $THISHOST ]; then
"${RUNDIR}/esx-report.sh" >> "$TEMPTEXT"
else
ssh -q $host "$(cat "${RUNDIR}/esx-report.sh")" >> "$TEMPTEXT" || \
printf "WARNING: SSH connection to $host failed\n\n\n\n" >> "$TEMPTEXT"
fi
done
cat > "$TEMPHTML" <<-'HEADEREOF'
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style type="text/css">
body { font-family: monospace; font-size: 12px }
pre { font-family: monospace; font-size: 12px }
</style>
</head>
<body>
<pre>
HEADEREOF
cat "$TEMPTEXT" | \
sed -e 's/>/\>/g' \
-e 's/WARNING:.*/<span style="color: red">&<\/span>/' >> "$TEMPHTML"
cat >> "$TEMPHTML" <<-'FOOTEREOF'
</pre>
</body>
</html>
FOOTEREOF
"${RUNDIR}/html-mailer.pl" -f esx-report@yourdomain.dom \
-r administrator@yourdomain.dom \
-s "ESX Health Report" \
-m exchange.yourdomain.com \
-b "$TEMPHTML"
rm -f "$TEMPTEXT"; rm -f "$TEMPHTML"
SCRIPTCREATOR
chmod 0700 ./run-esx-report.sh
###############################################################################
|
To cron foo, thanks for everything
Still with us? One more step, and it's an easy one. We'll add a cron job to run the script at 7:10 AM every morning. Remember to add the job for the user account you distributed SSH keys for.
To edit the cron entries for the user, type:
crontab -e
This starts vi and opens up the user's crontab. To enter insert mode, type i
Assuming you've set everything up using the code segments used in this series, to add an entry for 7:10 AM, type this line, replacing ESX LIST with a space separated list of ESX hosts:
10 7 * * * ${HOME}/esx-report/run-esx-report.sh ESX LIST >/dev/null 2>&1
After adding the entry, press the Esc key, and type :wq to write the crontab and quit.
If you have a long list of hosts, put them all in a text file, separated by spaces or each on its own line, and use command substitution to feed the list to run-esx-report.sh
run-esx-report.sh $(cat ${HOME}/esx-report/hostlist.txt)
There's more?!
What if we wanted to trigger an email warning if an ESX host exceeds a threshold value? As we'll see in Part 4, we can do this easily with a quick modification to the run-esx-report.sh script.
...read more