Step 1: Move vm scripts to the right place
[integration/test.git] / tools / wcbench / wcbench.sh
1 #!/usr/bin/env sh
2 # Main WCBench script. WCBench wraps CBench in stuff to make it useful.
3 # This script supports installing ODL, installing CBench, starting and
4 # configuring ODL, running CBench against ODL, pinning ODL to a given
5 # number of CPUs, using given-length CBench runs, collecting CBench
6 # results and system stats, storing results in CSV format, stopping
7 # ODL and removing all source/binaries installed by this script.
8 # The main repo for WCBench is: https://github.com/dfarrell07/wcbench
9 # See README.md for more details.
10
11 # Exit codes
12 EX_USAGE=64
13 EX_NOT_FOUND=65
14 EX_OK=0
15 EX_ERR=1
16
17 # Output verbose debug info (true) or not (anything else)
18 VERBOSE=false
19
20 # Params for CBench test and ODL config
21 NUM_SWITCHES=32 # Default number of switches for CBench to simulate
22 NUM_MACS=100000  # Default number of MACs for CBench to use
23 TESTS_PER_SWITCH=10  # Default number of CBench tests to do per CBench run
24 MS_PER_TEST=10000  # Default milliseconds to run each CBench test
25 CBENCH_WARMUP=1  # Default number of warmup cycles to run CBench
26 KARAF_SHELL_PORT=8101  # Port that the Karaf shell listens on
27 CONTROLLER="OpenDaylight"  # Currently only support ODL
28 CONTROLLER_IP="localhost"  # Change this to remote IP if running on two systems
29 CONTROLLER_PORT=6633  # Default port for OpenDaylight
30 SSH_HOSTNAME="cbenchc"  # You'll need to update this to reflect ~/.ssh/config
31
32 # Paths used in this script
33 BASE_DIR=$HOME  # Directory that code and such is dropped into
34 OF_DIR=$BASE_DIR/openflow  # Directory that contains OpenFlow code
35 OFLOPS_DIR=$BASE_DIR/oflops  # Directory that contains oflops repo
36 ODL_DIR=$BASE_DIR/distribution-karaf-0.2.1-Helium-SR1  # Directory with ODL code
37 ODL_ZIP="distribution-karaf-0.2.1-Helium-SR1.zip"  # ODL zip name
38 ODL_ZIP_PATH=$BASE_DIR/$ODL_ZIP  # Full path to ODL zip
39 PLUGIN_DIR=$ODL_DIR/plugins  # ODL plugin directory
40 RESULTS_FILE=$BASE_DIR/"results.csv"  # File that results are stored in
41 CBENCH_LOG=$BASE_DIR/"cbench.log"  # Log file used to store strange error msgs
42 CBENCH_BIN="/usr/local/bin/cbench"  # Path to CBench binary
43 OFLOPS_BIN="/usr/local/bin/oflops"  # Path to oflops binary
44 FEATURES_FILE=$ODL_DIR/etc/org.apache.karaf.features.cfg  # Karaf features to install
45
46 # Array that stores results in indexes defined by cols array
47 declare -a results
48
49 # The order of these array values determines column order in RESULTS_FILE
50 cols=(run_num cbench_min cbench_max cbench_avg start_time end_time
51     controller_ip human_time num_switches num_macs tests_per_switch
52     ms_per_test start_steal_time end_steal_time total_ram used_ram
53     free_ram cpus one_min_load five_min_load fifteen_min_load controller
54     start_iowait end_iowait)
55
56 # This two-stat-array system is needed until I find an answer to this question:
57 # http://goo.gl/e0M8Tp
58
59 # Associative array with stats-collecting commands for local system
60 declare -A local_stats_cmds
61 local_stats_cmds=([total_ram]="$(free -m | awk '/^Mem:/{print $2}')"
62             [used_ram]="$(free -m | awk '/^Mem:/{print $3}')"
63             [free_ram]="$(free -m | awk '/^Mem:/{print $4}')"
64             [cpus]="`nproc`"
65             [one_min_load]="`uptime | awk -F'[a-z]:' '{print $2}' | awk -F "," '{print $1}' | tr -d " "`"
66             [five_min_load]="`uptime | awk -F'[a-z]:' '{print $2}' | awk -F "," '{print $2}' | tr -d " "`"
67             [fifteen_min_load]="`uptime | awk -F'[a-z]:' '{print $2}' | awk -F "," '{print $3}' | tr -d " "`"
68             [iowait]="`cat /proc/stat | awk 'NR==1 {print $6}'`"
69             [steal_time]="`cat /proc/stat | awk 'NR==1 {print $9}'`")
70
71 # Associative array with stats-collecting commands for remote system
72 # See this for explanation of horrible-looking quoting: http://goo.gl/PMI5ag
73 declare -A remote_stats_cmds
74 remote_stats_cmds=([total_ram]='free -m | awk '"'"'/^Mem:/{print $2}'"'"''
75             [used_ram]='free -m | awk '"'"'/^Mem:/{print $3}'"'"''
76             [free_ram]='free -m | awk '"'"'/^Mem:/{print $4}'"'"''
77             [cpus]='nproc'
78             [one_min_load]='uptime | awk -F'"'"'[a-z]:'"'"' '"'"'{print $2}'"'"' | awk -F "," '"'"'{print $1}'"'"' | tr -d " "'
79             [five_min_load]='uptime | awk -F'"'"'[a-z]:'"'"' '"'"'{print $2}'"'"' | awk -F "," '"'"'{print $2}'"'"' | tr -d " "'
80             [fifteen_min_load]='uptime | awk -F'"'"'[a-z]:'"'"' '"'"'{print $2}'"'"' | awk -F "," '"'"'{print $3}'"'"' | tr -d " "'
81             [iowait]='cat /proc/stat | awk '"'"'NR==1 {print $6}'"'"''
82             [steal_time]='cat /proc/stat | awk '"'"'NR==1 {print $9}'"'"'')
83
84 ###############################################################################
85 # Prints usage message
86 # Globals:
87 #   None
88 # Arguments:
89 #   None
90 # Returns:
91 #   None
92 ###############################################################################
93 usage()
94 {
95     cat << EOF
96 Usage $0 [options]
97
98 Setup and/or run CBench and/or OpenDaylight.
99
100 OPTIONS:
101     -h Show this message
102     -v Output verbose debug info
103     -c Install CBench
104     -t <time> Run CBench for given number of minutes
105     -r Run CBench against OpenDaylight
106     -i Install OpenDaylight Helium 0.2.1
107     -p <processors> Pin ODL to given number of processors
108     -o Start and configure OpenDaylight Helium 0.2.1
109     -k Kill OpenDaylight
110     -d Delete local ODL and CBench code
111 EOF
112 }
113
114 ###############################################################################
115 # Checks if CBench is installed
116 # Globals:
117 #   EX_OK
118 #   EX_NOT_FOUND
119 # Arguments:
120 #   None
121 # Returns:
122 #   EX_OK if CBench is installed
123 #   EX_NOT_FOUND if CBench isn't installed
124 ###############################################################################
125 cbench_installed()
126 {
127     # Checks if CBench is installed
128     if command -v cbench &>/dev/null; then
129         echo "CBench is installed"
130         return $EX_OK
131     else
132         echo "CBench is not installed"
133         return $EX_NOT_FOUND
134     fi
135 }
136
137 ###############################################################################
138 # Installs CBench, including its dependencies
139 # This function is idempotent
140 # This has been tested on fresh cloud versions of Fedora 20 and CentOS 6.5
141 # Not currently building oflops/netfpga-packet-generator-c-library (optional)
142 # Globals:
143 #   VERBOSE
144 #   EX_OK
145 #   EX_ERR
146 #   OFLOPS_DIR
147 #   OF_DIR
148 # Arguments:
149 #   None
150 # Returns:
151 #   EX_OK if CBench is already installed or successfully installed
152 #   EX_ERR if CBench fails to install
153 ###############################################################################
154 install_cbench()
155 {
156     if cbench_installed; then
157         return $EX_OK
158     fi
159
160     # Install required packages
161     echo "Installing CBench dependencies"
162     if "$VERBOSE" = true; then
163         sudo yum install -y net-snmp-devel libpcap-devel autoconf make automake libtool libconfig-devel git
164     else
165         sudo yum install -y net-snmp-devel libpcap-devel autoconf make automake libtool libconfig-devel git &> /dev/null
166     fi
167
168     # Clone repo that contains CBench
169     echo "Cloning CBench repo into $OFLOPS_DIR"
170     if "$VERBOSE" = true; then
171         git clone https://github.com/andi-bigswitch/oflops.git $OFLOPS_DIR
172     else
173         git clone https://github.com/andi-bigswitch/oflops.git $OFLOPS_DIR &> /dev/null
174     fi
175
176     # CBench requires the OpenFlow source code, clone it
177     echo "Cloning openflow source code into $OF_DIR"
178     if "$VERBOSE" = true; then
179         git clone git://gitosis.stanford.edu/openflow.git $OF_DIR
180     else
181         git clone git://gitosis.stanford.edu/openflow.git $OF_DIR &> /dev/null
182     fi
183
184     # Build the oflops/configure file
185     old_cwd=$PWD
186     cd $OFLOPS_DIR
187     echo "Building oflops/configure file"
188     if "$VERBOSE" = true; then
189         ./boot.sh
190     else
191         ./boot.sh &> /dev/null
192     fi
193
194     # Build oflops
195     echo "Building CBench"
196     if "$VERBOSE" = true; then
197         ./configure --with-openflow-src-dir=$OF_DIR
198         make
199         sudo make install
200     else
201         ./configure --with-openflow-src-dir=$OF_DIR &> /dev/null
202         make &> /dev/null
203         sudo make install &> /dev/null
204     fi
205     cd $old_cwd
206
207     # Validate that the install worked
208     if ! cbench_installed; then
209         echo "Failed to install CBench" >&2
210         exit $EX_ERR
211     else
212         echo "Successfully installed CBench"
213         return $EX_OK
214     fi
215 }
216
217 ###############################################################################
218 # Get the number of the next run, as found in results file
219 # Assumes that the results file hasn't had rows removed by a human
220 # Globals:
221 #   RESULTS_FILE
222 # Arguments:
223 #   None
224 # Returns:
225 #   The run number of the next run
226 ###############################################################################
227 next_run_num()
228 {
229     # Check if there's actually a results file
230     if [ ! -s $RESULTS_FILE ]; then
231         echo 0
232         return
233     fi
234
235     # There should be one header row, then rows starting with 0, counting up
236     num_lines=`wc -l $RESULTS_FILE | awk '{print $1}'`
237     echo $(expr $num_lines - 1)
238 }
239
240 ###############################################################################
241 # Given the name of a results column, get its index in cols (therefore results)
242 # Globals:
243 #   cols
244 # Arguments:
245 #   The name of the column to find the index of
246 # Returns:
247 #   The index of the given column name in cols
248 ###############################################################################
249 name_to_index()
250 {
251     name=$1
252     for (( i = 0; i < ${#cols[@]}; i++ )); do
253         if [ "${cols[$i]}" = $name ]; then
254             echo $i
255             return
256         fi
257     done
258 }
259
260 ###############################################################################
261 # Accepts an array and writes it in CSV format to the results file
262 # Globals:
263 #   RESULTS_FILE
264 # Arguments:
265 #   Array to write to results file
266 # Returns:
267 #   None
268 ###############################################################################
269 write_csv_row()
270 {
271     # Creates var for array argument
272     declare -a array_to_write=("${!1}")
273     i=0
274     # Write all but the last column of the array to results file
275     while [ $i -lt $(expr ${#array_to_write[@]} - 1) ]; do
276         # Only use echo with comma and no newline for all but last col
277         echo -n "${array_to_write[$i]}," >> $RESULTS_FILE
278         let i+=1
279     done
280     # Finish CSV row with no comma and a newline
281     echo "${array_to_write[$i]}" >> $RESULTS_FILE
282 }
283
284 ###############################################################################
285 # Collects local or remote system stats that should be collected pre-CBench
286 # Pre and post-test collection is needed for computing the change in stats
287 # Globals:
288 #   CONTROLLER_IP
289 #   SSH_HOSTNAME
290 #   results
291 #   local_stats_commands
292 #   remote_stats_commands
293 # Arguments:
294 #   None
295 # Returns:
296 #   None
297 ###############################################################################
298 get_pre_test_stats()
299 {
300     echo "Collecting pre-test stats"
301     results[$(name_to_index "start_time")]=`date +%s`
302     if [ $CONTROLLER_IP = "localhost" ]; then
303         results[$(name_to_index "start_iowait")]=${local_stats_cmds[iowait]}
304         results[$(name_to_index "start_steal_time")]=${local_stats_cmds[steal_time]}
305     else
306         results[$(name_to_index "start_iowait")]=$(ssh $SSH_HOSTNAME "${remote_stats_cmds[iowait]}" 2> /dev/null)
307         results[$(name_to_index "start_steal_time")]=$(ssh $SSH_HOSTNAME "${remote_stats_cmds[steal_time]}" 2> /dev/null)
308     fi
309 }
310
311 ###############################################################################
312 # Collects local or remote system stats that should be collected post-CBench
313 # Pre and post-test collection is needed for computing the change in stats
314 # Globals:
315 #   CONTROLLER_IP
316 #   SSH_HOSTNAME
317 #   results
318 #   local_stats_commands
319 #   remote_stats_commands
320 # Arguments:
321 #   None
322 # Returns:
323 #   None
324 ###############################################################################
325 get_post_test_stats()
326 {
327     # Start by collecting always-local stats that are time-sensitive
328     echo "Collecting post-test stats"
329     results[$(name_to_index "end_time")]=`date +%s`
330     results[$(name_to_index "human_time")]=`date`
331
332     # Now collect local/remote stats that are time-sensative
333     if [ $CONTROLLER_IP = "localhost" ]; then
334         results[$(name_to_index "end_iowait")]=${local_stats_cmds[iowait]}
335         results[$(name_to_index "end_steal_time")]=${local_stats_cmds[steal_time]}
336         results[$(name_to_index "one_min_load")]=${local_stats_cmds[one_min_load]}
337         results[$(name_to_index "five_min_load")]=${local_stats_cmds[five_min_load]}
338         results[$(name_to_index "fifteen_min_load")]=${local_stats_cmds[fifteen_min_load]}
339     else
340         results[$(name_to_index "end_iowait")]=$(ssh $SSH_HOSTNAME "${remote_stats_cmds[iowait]}" 2> /dev/null)
341         results[$(name_to_index "end_steal_time")]=$(ssh $SSH_HOSTNAME "${remote_stats_cmds[steal_time]}" 2> /dev/null)
342         results[$(name_to_index "one_min_load")]=$(ssh $SSH_HOSTNAME "${remote_stats_cmds[one_min_load]}" 2> /dev/null)
343         results[$(name_to_index "five_min_load")]=$(ssh $SSH_HOSTNAME "${remote_stats_cmds[five_min_load]}" 2> /dev/null)
344         results[$(name_to_index "fifteen_min_load")]=$(ssh $SSH_HOSTNAME "${remote_stats_cmds[fifteen_min_load]}" 2> /dev/null)
345     fi
346 }
347
348 ###############################################################################
349 # Collects local or remote system stats for which collection time is irrelevant
350 # Globals:
351 #   CONTROLLER_IP
352 #   NUM_SWITCHES
353 #   NUM_MACS
354 #   TESTS_PER_SWITCH
355 #   MS_PER_TEST
356 #   CONTROLLER
357 #   SSH_HOSTNAME
358 #   results
359 #   local_stats_commands
360 #   remote_stats_commands
361 # Arguments:
362 #   None
363 # Returns:
364 #   None
365 ###############################################################################
366 get_time_irrelevant_stats()
367 {
368     # Collect always-local stats that aren't time-sensitive
369     echo "Collecting time-irrelevant stats"
370     results[$(name_to_index "run_num")]=$(next_run_num)
371     results[$(name_to_index "controller_ip")]=$CONTROLLER_IP
372     results[$(name_to_index "num_switches")]=$NUM_SWITCHES
373     results[$(name_to_index "num_macs")]=$NUM_MACS
374     results[$(name_to_index "tests_per_switch")]=$TESTS_PER_SWITCH
375     results[$(name_to_index "ms_per_test")]=$MS_PER_TEST
376     results[$(name_to_index "controller")]=$CONTROLLER
377
378     # Store local or remote stats that aren't time-sensitive
379     if [ $CONTROLLER_IP = "localhost" ]; then
380         results[$(name_to_index "total_ram")]=${local_stats_cmds[total_ram]}
381         results[$(name_to_index "used_ram")]=${local_stats_cmds[used_ram]}
382         results[$(name_to_index "free_ram")]=${local_stats_cmds[free_ram]}
383         results[$(name_to_index "cpus")]=${local_stats_cmds[cpus]}
384     else
385         results[$(name_to_index "total_ram")]=$(ssh $SSH_HOSTNAME "${remote_stats_cmds[total_ram]}" 2> /dev/null)
386         results[$(name_to_index "used_ram")]=$(ssh $SSH_HOSTNAME "${remote_stats_cmds[used_ram]}" 2> /dev/null)
387         results[$(name_to_index "free_ram")]=$(ssh $SSH_HOSTNAME "${remote_stats_cmds[free_ram]}" 2> /dev/null)
388         results[$(name_to_index "cpus")]=$(ssh $SSH_HOSTNAME "${remote_stats_cmds[cpus]}" 2> /dev/null)
389     fi
390 }
391
392 ###############################################################################
393 # Write data stored in results array to results file
394 # Globals:
395 #   RESULTS_FILE
396 #   cols
397 #   results
398 # Arguments:
399 #   None
400 # Returns:
401 #   None
402 ###############################################################################
403 write_results()
404 {
405     # Write header if this is a fresh results file
406     if [ ! -s $RESULTS_FILE ]; then
407         echo "$RESULTS_FILE not found or empty, building fresh one" >&2
408         write_csv_row cols[@]
409     fi
410     write_csv_row results[@]
411 }
412
413 ###############################################################################
414 # Runs the CBench against the controller
415 # Globals:
416 #   CONTROLLER_IP
417 #   CONTROLLER_PORT
418 #   VERBOSE
419 #   MS_PER_TEST
420 #   TEST_PER_SWITCH
421 #   NUM_SWITCHES
422 #   NUM_MACS
423 #   CBENCH_WARMUP
424 #   CBENCH_LOG
425 #   results
426 # Arguments:
427 #   None
428 # Returns:
429 #   None
430 ###############################################################################
431 run_cbench()
432 {
433     get_pre_test_stats
434     echo "Running CBench against ODL on $CONTROLLER_IP:$CONTROLLER_PORT"
435     if "$VERBOSE" = true; then
436         cbench_output=`cbench -c $CONTROLLER_IP -p $CONTROLLER_PORT -m $MS_PER_TEST -l $TESTS_PER_SWITCH -s $NUM_SWITCHES -M $NUM_MACS -w $CBENCH_WARMUP`
437     else
438         cbench_output=`cbench -c $CONTROLLER_IP -p $CONTROLLER_PORT -m $MS_PER_TEST -l $TESTS_PER_SWITCH -s $NUM_SWITCHES -M $NUM_MACS -w $CBENCH_WARMUP 2>&1`
439     fi
440     get_post_test_stats
441     get_time_irrelevant_stats
442
443     # Parse out min, max and average responses/sec, log/handle errors
444     # See: https://github.com/dfarrell07/wcbench/issues/16
445     cbench_min=`echo "$cbench_output" | grep RESULT | awk '{print $8}' | awk -F'/' '{print $1}'`
446     cbench_max=`echo "$cbench_output" | grep RESULT | awk '{print $8}' | awk -F'/' '{print $2}'`
447     cbench_avg=`echo "$cbench_output" | grep RESULT | awk '{print $8}' | awk -F'/' '{print $3}'`
448     if [ -z "$cbench_avg" ]; then
449         echo "WARNING: Error occurred: Failed to parse CBench average" >&2
450         echo "This is an issue with CBench or ODL, not WCBench." >&2
451         echo "May need to reduce NUM_SWITCHES or allocate more CPU cores" >&2
452         echo "See: $CBENCH_LOG" >&2
453         echo "See: https://github.com/dfarrell07/wcbench/issues/16" >&2
454         echo "Run $(next_run_num) failed to record a CBench average. CBench details:" >> $CBENCH_LOG
455         echo "$cbench_output" >> $CBENCH_LOG
456         return
457     else
458         echo "Average responses/second: $cbench_avg"
459         results[$(name_to_index "cbench_min")]=$cbench_min
460         results[$(name_to_index "cbench_max")]=$cbench_max
461         results[$(name_to_index "cbench_avg")]=$cbench_avg
462     fi
463
464     # Write results to results file
465     write_results
466 }
467
468 ###############################################################################
469 # Deletes OpenDaylight source (zipped and unzipped)
470 # Globals:
471 #   ODL_DIR
472 #   ODL_ZIP_PATH
473 # Arguments:
474 #   None
475 # Returns:
476 #   None
477 ###############################################################################
478 uninstall_odl()
479 {
480     if [ -d $ODL_DIR ]; then
481         echo "Removing $ODL_DIR"
482         rm -rf $ODL_DIR
483     fi
484     if [ -f $ODL_ZIP_PATH ]; then
485         echo "Removing $ODL_ZIP_PATH"
486         rm -f $ODL_ZIP_PATH
487     fi
488 }
489
490 ###############################################################################
491 # Checks if the given feature is in list to be installed at boot
492 # Globals:
493 #   FEATURES_FILE
494 #   EX_OK
495 #   EX_NOT_FOUND
496 # Arguments:
497 #   Feature to search featuresBoot list for
498 # Returns:
499 #   EX_OK if feature already in featuresBoot list
500 #   EX_NOT_FOUND if feature isn't in featuresBoot list
501 ###############################################################################
502 is_in_featuresBoot()
503 {
504     feature=$1
505
506     # Check if feature is already set to be installed at boot
507     if $(grep featuresBoot= $FEATURES_FILE | grep -q $feature); then
508         return $EX_OK
509     else
510         return $EX_NOT_FOUND
511     fi
512 }
513
514 ###############################################################################
515 # Adds features to be installed by Karaf at ODL boot
516 # Globals:
517 #   FEATURES_FILE
518 #   EX_OK
519 #   EX_ERR
520 # Arguments:
521 #   Feature to append to end of featuresBoot CSV list
522 # Returns:
523 #   EX_OK if feature already is installed or was successfully added
524 #   EX_ERR if failed to add feature to group installed at boot
525 ###############################################################################
526 add_to_featuresBoot()
527 {
528     feature=$1
529
530     # Check if feature is already set to be installed at boot
531     if is_in_featuresBoot $feature; then
532         echo "$feature is already set to be installed at boot"
533         return $EX_OK
534     fi
535
536     # Append feature to end of boot-install list
537     sed -i "/^featuresBoot=/ s/$/,$feature/" $FEATURES_FILE
538
539     # Check if feature was added to install list correctly
540     if is_in_featuresBoot $feature; then
541         echo "$feature added to features installed at boot"
542         return $EX_OK
543     else
544         echo "ERROR: Failed to add $feature to features installed at boot"
545         return $EX_ERR
546     fi
547 }
548
549 ###############################################################################
550 # Installs latest build of the OpenDaylight controller
551 # Note that the installed build is via an Integration team Jenkins job
552 # Globals:
553 #   ODL_DIR
554 #   VERBOSE
555 #   ODL_ZIP_DIR
556 #   BASE_DIR
557 #   ODL_ZIP_PATH
558 #   ODL_ZIP
559 #   EX_ERR
560 # Arguments:
561 #   None
562 # Returns:
563 #   EX_ERR if ODL install fails
564 ###############################################################################
565 install_opendaylight()
566 {
567     # Only remove unzipped code, as zip is large and unlikely to have changed.
568     if [ -d $ODL_DIR ]; then
569         echo "Removing $ODL_DIR"
570         rm -rf $ODL_DIR
571     fi
572
573     # Install required packages
574     echo "Installing OpenDaylight dependencies"
575     if "$VERBOSE" = true; then
576         sudo yum install -y java-1.7.0-openjdk unzip wget
577     else
578         sudo yum install -y java-1.7.0-openjdk unzip wget &> /dev/null
579     fi
580
581     # If we already have the zip archive, use that.
582     if [ -f $ODL_ZIP_PATH ]; then
583         echo "Using local $ODL_ZIP_PATH. Pass -d flag to remove."
584     else
585         # Grab OpenDaylight Helium 0.2.1
586         echo "Downloading OpenDaylight Helium 0.2.1"
587         if "$VERBOSE" = true; then
588             wget -P $BASE_DIR "https://nexus.opendaylight.org/content/groups/public/org/opendaylight/integration/distribution-karaf/0.2.1-Helium-SR1/$ODL_ZIP"
589         else
590             wget -P $BASE_DIR "https://nexus.opendaylight.org/content/groups/public/org/opendaylight/integration/distribution-karaf/0.2.1-Helium-SR1/$ODL_ZIP" &> /dev/null
591         fi
592     fi
593
594     # Confirm that download was successful
595     if [ ! -f $ODL_ZIP_PATH ]; then
596         echo "WARNING: Failed to dl ODL. Version bumped? If so, update \$ODL_ZIP" >&2
597         return $EX_ERR
598     fi
599
600     # Unzip ODL archive
601     echo "Unzipping OpenDaylight Helium 0.2.1"
602     if "$VERBOSE" = true; then
603         unzip -d $BASE_DIR $ODL_ZIP_PATH
604     else
605         unzip -d $BASE_DIR $ODL_ZIP_PATH &> /dev/null
606     fi
607
608     # Add required features to list installed by Karaf at ODL boot
609     add_to_featuresBoot "odl-openflowplugin-flow-services"
610     add_to_featuresBoot "odl-openflowplugin-drop-test"
611 }
612
613 ###############################################################################
614 # Checks if OpenDaylight is installed
615 # Globals:
616 #   ODL_DIR
617 #   EX_NOT_FOUND
618 # Arguments:
619 #   None
620 # Returns:
621 #   EX_NOT_FOUND if ODL isn't installed
622 #   0 if ODL is installed
623 ###############################################################################
624 odl_installed()
625 {
626     if [ ! -d $ODL_DIR ]; then
627         return $EX_NOT_FOUND
628     fi
629 }
630
631 ###############################################################################
632 # Checks if OpenDaylight is running
633 # Assumes you've checked that ODL is installed
634 # Globals:
635 #   ODL_DIR
636 #   VERBOSE
637 #   EX_OK
638 #   EX_NOT_FOUND
639 # Arguments:
640 #   None
641 # Returns:
642 #   EX_OK if ODL is running
643 #   EX_NOT_FOUND if ODL isn't running
644 ###############################################################################
645 odl_started()
646 {
647     old_cwd=$PWD
648     cd $ODL_DIR
649     if "$VERBOSE" = true; then
650         ./bin/status
651     else
652         ./bin/status &> /dev/null
653     fi
654     if [ $? = 0 ]; then
655         return $EX_OK
656     else
657         return $EX_NOT_FOUND
658     fi
659     cd $old_cwd
660 }
661
662 ###############################################################################
663 # Starts the OpenDaylight controller
664 # Pins ODL process to given number of CPUs if `$processors` is non-zero
665 # Makes call to issue ODL config once ODL is up and running
666 # Globals:
667 #   ODL_DIR
668 #   EX_OK
669 #   processors
670 #   VERBOSE
671 # Arguments:
672 #   None
673 # Returns:
674 #   EX_OK if ODL is already running
675 ###############################################################################
676 start_opendaylight()
677 {
678     old_cwd=$PWD
679     cd $ODL_DIR
680     if odl_started; then
681         echo "OpenDaylight is already running"
682         return $EX_OK
683     else
684         echo "Starting OpenDaylight"
685         if [ -z $processors ]; then
686             if "$VERBOSE" = true; then
687                 ./bin/start
688             else
689                 ./bin/start &> /dev/null
690             fi
691         else
692             echo "Pinning ODL to $processors processor(s)"
693             # Use taskset to pin ODL to a given number of processors
694             if "$VERBOSE" = true; then
695                 taskset -c 0-$(expr $processors - 1) ./bin/start
696             else
697                 taskset -c 0-$(expr $processors - 1) ./bin/start  &> /dev/null
698             fi
699         fi
700     fi
701     cd $old_cwd
702     issue_odl_config
703 }
704
705 ###############################################################################
706 # Set `dropAllPackets on` and log level to DEBUG via Karaf shell
707 # Globals:
708 #   VERBOSE
709 #   KARAF_SHELL_PORT
710 # Arguments:
711 #   None
712 # Returns:
713 #   None
714 ###############################################################################
715 issue_odl_config()
716 {
717     # This could be done with public key crypto, but sshpass is easier
718     if ! command -v sshpass &> /dev/null; then
719         echo "Installing sshpass. It's used for issuing ODL config."
720         if "$VERBOSE" = true; then
721             sudo yum install -y sshpass
722         else
723             sudo yum install -y sshpass &> /dev/null
724         fi
725     fi
726
727     # Set `dropAllPacketsRpc on`
728     echo "Will repeatedly attempt connecting to Karaf shell until it's ready"
729     # Loop until exit status 0 (success) given by Karaf shell
730     # Exit status 255 means Karaf shell isn't open for SSH connections yet
731     # Exit status 1 means `dropAllPacketsRpc on` isn't runnable yet
732     if "$VERBOSE" = true; then
733         until sshpass -p karaf ssh -p $KARAF_SHELL_PORT -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no karaf@localhost dropallpacketsrpc on
734         do
735             echo "Karaf shell isn't ready yet, sleeping 5 seconds..."
736             sleep 5
737         done
738     else
739         until sshpass -p karaf ssh -p $KARAF_SHELL_PORT -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no karaf@localhost dropallpacketsrpc on &> /dev/null
740         do
741             sleep 5
742         done
743     fi
744     echo "Issued \`dropAllPacketsRpc on\` command via Karaf shell to localhost:$KARAF_SHELL_PORT"
745
746     # Change log level to ERROR
747     # Loop until exit status 0 (success) given by Karaf shell
748     # Exit status 255 means Karaf shell isn't open for SSH connections yet
749     if "$VERBOSE" = true; then
750         until sshpass -p karaf ssh -p $KARAF_SHELL_PORT -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no karaf@localhost log:set ERROR
751         do
752             echo "Karaf shell isn't ready yet, sleeping 5 seconds..."
753             sleep 5
754         done
755     else
756         until sshpass -p karaf ssh -p $KARAF_SHELL_PORT -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no karaf@localhost log:set ERROR &> /dev/null
757         do
758             sleep 5
759         done
760     fi
761     echo "Issued \`log:set ERROR\` command via Karaf shell to localhost:$KARAF_SHELL_PORT"
762 }
763
764 ###############################################################################
765 # Stops OpenDaylight
766 # Globals:
767 #   ODL_DIR
768 #   VERBOSE
769 # Arguments:
770 #   None
771 # Returns:
772 #   None
773 ###############################################################################
774 stop_opendaylight()
775 {
776     old_cwd=$PWD
777     cd $ODL_DIR
778     if odl_started; then
779         echo "Told ODL to stop. Waiting on it to do so..."
780         echo "This check is useless if you have other Java processes running (ctrl+c it)."
781         if "$VERBOSE" = true; then
782             ./bin/stop
783         else
784             ./bin/stop &> /dev/null
785         fi
786         # Loop until actually stopped
787         until ! pgrep java &> /dev/null
788         do
789             sleep .5
790         done
791         echo "OpenDaylight has stopped."
792     else
793         echo "OpenDaylight isn't running"
794     fi
795     cd $old_cwd
796 }
797
798 ###############################################################################
799 # Uninstall CBench binary and the code that built it
800 # Globals:
801 #   OF_DIR
802 #   OFLOPS_DIR
803 #   CBENCH_BIN
804 # Arguments:
805 #   None
806 # Returns:
807 #   None
808 ###############################################################################
809 uninstall_cbench()
810 {
811     if [ -d $OF_DIR ]; then
812         echo "Removing $OF_DIR"
813         rm -rf $OF_DIR
814     fi
815     if [ -d $OFLOPS_DIR ]; then
816         echo "Removing $OFLOPS_DIR"
817         rm -rf $OFLOPS_DIR
818     fi
819     if [ -f $CBENCH_BIN ]; then
820         echo "Removing $CBENCH_BIN"
821         sudo rm -f $CBENCH_BIN
822     fi
823     if [ -f $OFLOPS_BIN ]; then
824         echo "Removing $OFLOPS_BIN"
825         sudo rm -f $OFLOPS_BIN 
826     fi
827 }
828
829 # If executed with no options
830 if [ $# -eq 0 ]; then
831     usage
832     exit $EX_USAGE
833 fi
834
835 # Used to output help if no valid action results from arguments
836 action_taken=false
837
838 # Parse options given from command line
839 while getopts ":hvrcip:ot:kd" opt; do
840     case "$opt" in
841         h)
842             # Help message
843             usage
844             exit $EX_OK
845             ;;
846         v)
847             # Output debug info verbosely
848             VERBOSE=true
849             ;;
850         r)
851             # Run CBench against OpenDaylight
852             if [ $CONTROLLER_IP = "localhost" ]; then
853                 if ! odl_installed; then
854                     echo "OpenDaylight isn't installed, can't run test"
855                     exit $EX_ERR
856                 fi
857                 if ! odl_started; then
858                     echo "OpenDaylight isn't started, can't run test"
859                     exit $EX_ERR
860                 fi
861             fi
862             run_cbench
863             action_taken=true
864             ;;
865         c)
866             # Install CBench
867             install_cbench
868             action_taken=true
869             ;;
870         i)
871             # Install OpenDaylight
872             install_opendaylight
873             action_taken=true
874             ;;
875         p)
876             # Pin a given number of processors
877             # Note that this option must be given before -o (start ODL)
878             if odl_started; then
879                 echo "OpenDaylight is already running, can't adjust processors"
880                 exit $EX_ERR
881             fi
882             processors=${OPTARG}
883             if [ $processors -lt 1 ]; then
884                 echo "Can't pin ODL to less than one processor"
885                 exit $EX_USAGE
886             fi
887             ;;
888         o)
889             # Run OpenDaylight
890             if ! odl_installed; then
891                 echo "OpenDaylight isn't installed, can't start it"
892                 exit $EX_ERR
893             fi
894             start_opendaylight
895             action_taken=true
896             ;;
897         t)
898             # Set CBench run time in minutes
899             if ! odl_installed; then
900                 echo "OpenDaylight isn't installed, can't start it"
901                 exit $EX_ERR
902             fi
903             # Convert minutes to milliseconds
904             MS_PER_TEST=$((${OPTARG} * 60 * 1000))
905             TESTS_PER_SWITCH=1
906             CBENCH_WARMUP=0
907             echo "Set MS_PER_TEST to $MS_PER_TEST, TESTS_PER_SWITCH to $TESTS_PER_SWITCH, CBENCH_WARMUP to $CBENCH_WARMUP"
908             ;;
909         k)
910             # Kill OpenDaylight
911             if ! odl_installed; then
912                 echo "OpenDaylight isn't installed, can't stop it"
913                 exit $EX_ERR
914             fi
915             if ! odl_started; then
916                 echo "OpenDaylight isn't started, can't stop it"
917                 exit $EX_ERR
918             fi
919             stop_opendaylight
920             action_taken=true
921             ;;
922         d)
923             # Delete local ODL and CBench code
924             uninstall_odl
925             uninstall_cbench
926             action_taken=true
927             ;;
928         *)
929             # Print usage message
930             usage
931             exit $EX_USAGE
932     esac
933 done
934
935 # Output help message if no valid action was taken
936 if ! "$action_taken" = true; then
937     usage
938     exit $EX_USAGE
939 fi