Mail Injection Stress Test
Zimbra version and platform
This script was developed and tested on Release 7.2 Network Edition.
Introduction
The purpose of this script is to load test a server by injecting email at a specified rate into a number of test accounts. Existing email found in the mail store is used as a source of email as this probably represents typical current and future email. Of course, this needs a server with something in the mail store already to work properly, not a fresh install.
Features:
- Operates as "zimbra" user
- Should works on both Open Source or Network edition
- Email rate can be specified
- Test accounts for email dump can be specified
- Accounts can be locked to prevent anyone logging in and seeing everyone else's email
- Accounts can be flushed when finished
Precautions:
- Do not use on any account you need to keep, it will be flooded with junk
Configuration
A file in the same directory as the script is run, lmtp_injector_conf, must exist to specify the user accounts and batch size. The batch size is the number of emails that are injected into an account per zmlmtpinject command. The smaller the number the more accounts will be injected in parallel. If it is too small the email rate won't be reached. If the batch size is too large then all the emails will be injected into one account at a time which may not exercise the system so well.
Here is a typical config file, change account batch size and accounts as per your requirements.
message_batch_size="100" account_list="fred@example.com harry@example.com ned@example.com mary@example.com sue@example.com larry@example.com jane@example.com liz@example.com"
The script
#!/bin/bash # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/> ########################################################################## # Title : lmtp_injector # Author : Simon Blandford <simon -at- onepointltd -dt- com> # Date : 2012-05-02 # Requires : zimbra # Category : Administration # Version : 1.0.0 # Copyright : Simon Blandford, Onepoint Consulting Limited # License : GPLv3 (see above) ########################################################################## # Description # Inject email found in store to a list of accounts at specified rate ########################################################################## #Quit if not running as Zimbra if [ "x$( whoami )" != "xzimbra" ]; then echo "You must be user zimbra to run this" exit 1 fi message_store="/opt/zimbra/store/0" tmp_folder="/tmp/lmtp_injector" message_count_file="$tmp_folder"/"message_count" stats_run_file="$tmp_folder"/"run_stats" lmtp_injector_conf_file="lmtp_injector_conf" source "$lmtp_injector_conf_file" if ! echo "$account_list" | grep "@" >/dev/null; then echo "Account list not found in $lmtp_injector_conf_file" >&2 exit 1 fi if ! echo "message_batch_size" | egrep "[0-9]+"; then echo "Batch size not set in $lmtp_injector_conf_file" >&2 exit 1 fi account_list_length="$( echo "$account_list" | wc -l )" start_time=$( date "+%s" ) #Return message rate in messages per second convert_message_rate () { local input_rate arr input_rate="$1" #Split at forward slash arr=(${input_rate//\// }) input_number=$( echo ${arr[0]} | egrep "[0-9\.]+" ) case ${arr[1]} in second) echo "scale=12; $input_number / 1.0" | bc ;; minute) echo "scale=12; $input_number / 60.0" | bc ;; hour) echo "scale=12; $input_number / 3600.0" | bc ;; day) echo "scale=12; $input_number / 43200.0" | bc ;; *) echo "Bad rate. Format should be numeric/(second/minute/hour/day)." >&2 return 1 ;; esac } #Return the time in seconds since the start time_since_start () { echo $(( $( date "+%s" ) - start_time )) } #Return the numnber of messages that should have been sent by now message_target () { local message_rate message_rate="$1" #Return integer printf "%1.0f\n" "$( echo "$( time_since_start ) * $message_rate" | bc )" } print_stats_line () { local message_rate count message_rate="$1" printf "%-40s%-30s%-30s%-30s\n" \ "Message rate: $requested_rate" \ "Elapsed : $( time_since_start ) seconds" \ "Target sent: $( message_target "$message_rate " )" \ "Actual sent: $( head "$message_count_file" )" } #Show statistics show_stats () { local message_rate count touch "$stats_run_file" message_rate="$1" { while [ -f "$stats_run_file" ]; do if [ $(( i++ % 10 )) -eq 0 ]; then print_stats_line "$message_rate" fi sleep 1 done print_stats_line "$message_rate" } & } #Set the list of accounts to given state given as parameter set_accounts_state () { local zmcommand account state state=$1 for account in $( echo $account_list ); do zmcommand="$zmcommand"" ma $account zimbraAccountStatus $state" done echo "$zmcommand" | /opt/zimbra/bin/zmprov } #Return a random account from the list account_random () { while [ 1 ]; do account="$( echo "$account_list" | \ sed -n "$(( (RANDOM % account_list_length) + 1 )){p;q;}" )" [ ${#account} -gt 5 ] && break done echo "$account" } #Return a random directory from the message store drill_random () { local store item items store="$message_store" item="$store" ( while [ -d "$item" ]; do cd "$item" || return 1 items=$( ls -1 | wc -l ) if [ $items -eq 0 ]; then item="$store" continue fi item=$( ls -1 | sed -n "$(( (RANDOM % items) + 1 )){p;q;}" ) done echo "$( pwd )" ) } #Find a random directory that has enough messages in it for a batch get_full_directory () { local messages_in_dir selected_dir messages_in_dir=0 while [ $messages_in_dir -lt $message_batch_size ]; do selected_dir=$( drill_random ) messages_in_dir=$( ls -d "$selected_dir"/* | wc -l ) done echo "$messages_in_dir $selected_dir" } #Get a batch of files #Return the name of the directory on the first line #and the list of message files on the next line get_file_batch () { local message_dir_and_count message_count message_dir start_point message_dir_and_count=$( get_full_directory ) message_count=$( echo "$message_dir_and_count" | awk '{print $1}' ) message_dir=$( echo "$message_dir_and_count" | awk '{print $2}' ) echo "$message_dir" start_point=$(( (RANDOM % message_count) + 1 )) while [ $(( start_point + message_batch_size )) -ge $message_count ]; do start_point=$(( (RANDOM % message_count) + 1 )) done echo $( ls -1 "$message_dir" | sed -n "$start_point,$(( start_point + message_batch_size - 1 ))p" ) } #Flush the accounts flush_accounts () { local account for account in $( echo $account_list ); do echo "Flushing account: $account" /opt/zimbra/bin/zmmailbox -z -m "$account" emptyFolder /inbox done } #Inject a batch of messages with From address and To addresss #given as parameters inject_batch () { local dir_and_file_list directory file_list from_account=$1 to_account=$2 dir_and_file_list=$( get_file_batch ) directory=$( echo "$dir_and_file_list" | head -n 1 ) file_list=$( echo "$dir_and_file_list" | tail -n 1 ) #Create the lock directory or return fail mkdir "$tmp_folder""/""$to_account" 2>/dev/null || return 1 ( cd "$directory" #Run command in background and delete lock directory when done { /opt/zimbra/bin/zmlmtpinject -q -r "$from_account" -s "$to_account" $file_list rmdir "$tmp_folder""/""$to_account" } & ) #Return success return 0 } #Inject mail contained in a store directory into an email account inject_mail () { from_account=$1 to_account=$2 mail_directory=$3 [ $4 ] && flush="yes" #Silently quit if this account is already being injected mkdir "$tmp_folder""/""$to_account" 2>/dev/null || return echo "Injecting mail to account: $to_account from $mail_directory" stats=$( /opt/zimbra/bin/zmlmtpinject -N 9999 -r "$from_account" -s "$to_account" -d "$mail_directory" ) [ $flush ] && echo "Flushing mail from account: $to_account from $mail_directory" [ $flush ] && /opt/zimbra/bin/zmmailbox -z -m $to_account emptyFolder /inbox echo "$stats" echo "Done" rmdir "$tmp_folder""/""$to_account" } quit_out () { local time_out_counter echo echo "Quitting..." #Wait for batches to finish while ls -d "$tmp_folder""/"*@* &>/dev/null; do if [ $(( time_out_counter++ )) -gt 60 ]; then echo "Batches not finished in a minute. Giving up." >&2 echo "Please kill running processes manually." >&2 break; fi sleep 1 done rm -f "$stats_run_file" sleep 1 rm -rf "$tmp_folder" echo "Done" exit 0 } if [ "$1" ] && [ "$1" == "flush" ]; then flush_accounts exit fi if [ "$1" ] && [ "$1" == "lock" ]; then set_accounts_state locked exit fi #Trap Ctrl-C trap quit_out SIGINT #Make and clear temporary lock directory mkdir -p "$tmp_folder" 2>/dev/null rmdir "$tmp_folder"/* 2>/dev/null requested_rate="$1" message_rate=$( convert_message_rate "$requested_rate" ) || exit 1 echo "0" >"$message_count_file" show_stats "$message_rate" while [ 1 ]; do #Submit a new batch every time more messages are required to meet target if [ $( head "$message_count_file" ) -lt $( message_target "$message_rate " ) ] 2>/dev/null; then from_account=$( account_random ) to_acccount=$( account_random ) #If batch runs OK then add to total message count if inject_batch "$from_account" "$to_acccount"; then echo "$(( $( head "$message_count_file" ) + message_batch_size ))" >"$message_count_file" fi fi done