blob: 9eca39ce003001d4be393a73d5032ebe7b97c570 [file] [log] [blame]
Vince Lehmanb8b18062015-07-14 13:07:22 -05001# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
2#
Vince Lehman5d5a5662015-12-02 12:33:12 -06003# Copyright (C) 2015-2016, The University of Memphis,
4# Arizona Board of Regents,
5# Regents of the University of California.
Vince Lehmanb8b18062015-07-14 13:07:22 -05006#
7# This file is part of Mini-NDN.
8# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
9#
10# Mini-NDN is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 3 of the License, or
13# (at your option) any later version.
14#
15# Mini-NDN is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with Mini-NDN, e.g., in COPYING.md file.
22# If not, see <http://www.gnu.org/licenses/>.
23#
24# This file incorporates work covered by the following copyright and
25# permission notice:
26#
27# Mininet 2.2.1 License
28#
29# Copyright (c) 2013-2015 Open Networking Laboratory
30# Copyright (c) 2009-2012 Bob Lantz and The Board of Trustees of
31# The Leland Stanford Junior University
32#
33# Original authors: Bob Lantz and Brandon Heller
34#
35# We are making Mininet available for public use and benefit with the
36# expectation that others will use, modify and enhance the Software and
37# contribute those enhancements back to the community. However, since we
38# would like to make the Software available for broadest use, with as few
39# restrictions as possible permission is hereby granted, free of charge, to
40# any person obtaining a copy of this Software to deal in the Software
41# under the copyrights without restriction, including without limitation
42# the rights to use, copy, modify, merge, publish, distribute, sublicense,
43# and/or sell copies of the Software, and to permit persons to whom the
44# Software is furnished to do so, subject to the following conditions:
45#
46# The above copyright notice and this permission notice shall be included
47# in all copies or substantial portions of the Software.
48#
49# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
50# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
51# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
52# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
53# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
54# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
55# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
56#
57# The name and trademarks of copyright holder(s) may NOT be used in
58# advertising or publicity pertaining to the Software or any derivatives
59# without specific, written prior permission.
ashuef3490b2015-02-17 11:01:04 -060060
61from mininet.topo import Topo
62from mininet.net import Mininet
63from mininet.log import setLogLevel, output, info
64from mininet.cli import CLI
65from mininet.link import TCLink
ashu2ad32e22015-05-29 13:37:40 -050066from mininet.util import ipStr, ipParse
ashuef3490b2015-02-17 11:01:04 -060067
Vince Lehman3b8bc652015-06-18 15:01:47 -050068
69from ndn import ExperimentManager
ashuef3490b2015-02-17 11:01:04 -060070from ndn.ndn_host import NdnHost, CpuLimitedNdnHost
Vince Lehmanfbd47c92015-10-14 16:00:06 -050071from ndn.conf_parser import parse_hosts, parse_switches, parse_links
ashuef3490b2015-02-17 11:01:04 -060072
73import os.path, time
Yucheng Zhang14aa5962016-04-05 17:10:15 -050074import shutil
Ashlesh Gawande044611d2016-12-21 14:24:49 -060075import argparse
ashuef3490b2015-02-17 11:01:04 -060076import datetime
ashu01b62f72015-03-12 15:16:11 -050077from os.path import expanduser
Vince Lehman3b8bc652015-06-18 15:01:47 -050078import sys
Ashlesh Gawande3807c1b2016-08-05 16:27:02 -050079import signal
Yucheng Zhang14aa5962016-04-05 17:10:15 -050080from subprocess import call
ashuef3490b2015-02-17 11:01:04 -060081
82from ndn.nlsr import Nlsr, NlsrConfigGenerator
ashu34c3ee02015-03-25 14:41:14 -050083from ndn.nfd import Nfd
ashuef3490b2015-02-17 11:01:04 -060084
Ashlesh Gawande0cccdb82016-08-15 12:58:06 -050085VERSION_NUMBER = "0.2.0"
Ashlesh Gawande044611d2016-12-21 14:24:49 -060086INSTALL_DIR='/usr/local/etc/mini-ndn/'
Vince Lehmane9f116d2015-07-15 10:40:21 -050087
Ashlesh Gawande044611d2016-12-21 14:24:49 -060088class PrintExperimentNames(argparse.Action):
89 def __init__(self, option_strings, dest, nargs=0, help=None):
90 super(PrintExperimentNames, self).__init__(option_strings=option_strings, dest=dest, nargs=nargs, help=help)
Vince Lehman3b8bc652015-06-18 15:01:47 -050091
Ashlesh Gawande044611d2016-12-21 14:24:49 -060092 def __call__(self, parser, namespace, values, option_string=None):
93 experimentNames = ExperimentManager.getExperimentNames()
Vince Lehman3b8bc652015-06-18 15:01:47 -050094
Ashlesh Gawande044611d2016-12-21 14:24:49 -060095 print("Mini-NDN experiments:")
96 for experiment in experimentNames:
97 print(" %s" % experiment)
Vince Lehman194be242015-10-15 18:01:42 -050098
Ashlesh Gawande044611d2016-12-21 14:24:49 -060099 sys.exit(0)
Vince Lehmane9f116d2015-07-15 10:40:21 -0500100
Vince Lehman194be242015-10-15 18:01:42 -0500101class ProgramOptions:
102 def __init__(self):
103 self.ctime = 60
104 self.experimentName = None
105 self.nFaces = 3
106 self.templateFile = "minindn.conf"
107 self.hr = False
108 self.isCliEnabled = True
Vince Lehman5d5a5662015-12-02 12:33:12 -0600109 self.nlsrSecurity = False
Vince Lehman194be242015-10-15 18:01:42 -0500110 self.nPings = 300
111 self.testbed = False
Ashlesh Gawande1b663692015-10-14 16:38:10 -0500112 self.workDir = "/tmp"
Ashlesh Gawanded829bfc2015-10-14 16:38:10 -0500113 self.resultDir = None
Ashlesh Gawanded9c9e522015-10-15 16:40:12 -0500114 self.pctTraffic = 1.0
Ashlesh Gawanded829bfc2015-10-14 16:38:10 -0500115
116def createResultsDir(resultDir, faces, hr):
117 if faces == 0:
118 faces = "all"
119
120 if hr:
121 routingType = "/hr/"
122 else:
123 routingType = "/ls/"
124
125 resultDir = "%s/%s/faces-%s" % (str(resultDir), routingType, str(faces))
126 resultDir = os.path.abspath(resultDir)
127
128 if not os.path.isdir(resultDir):
129 os.makedirs(resultDir)
130 else:
131 print("Results directory (%s) already exists!" % resultDir)
132 sys.exit(1);
133
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600134 print("Results will be stored at: %s" % resultDir)
Ashlesh Gawanded829bfc2015-10-14 16:38:10 -0500135 return resultDir
Vince Lehman194be242015-10-15 18:01:42 -0500136
ashuef3490b2015-02-17 11:01:04 -0600137def parse_args():
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600138 parser = argparse.ArgumentParser(prog='minindn')
ashuef3490b2015-02-17 11:01:04 -0600139
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600140 # nargs='?' required here since optional argument
141 parser.add_argument('tempfile', nargs='?', default=INSTALL_DIR + 'default-topology.conf',
142 help="If no template_file is given, ndn_utils/default-topology.conf (given sample file) will be used.")
ashuef3490b2015-02-17 11:01:04 -0600143
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600144 parser.add_argument("--ctime", type=int, default=60,
145 help="Specify convergence time for the topology (Default: 60 seconds)")
ashuef3490b2015-02-17 11:01:04 -0600146
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600147 parser.add_argument("--experiment",
148 help="Runs the specified experiment")
Vince Lehman3b8bc652015-06-18 15:01:47 -0500149
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600150 parser.add_argument("--faces", type=int, default=3,
151 help="Specify number of faces 0-60")
ashuef3490b2015-02-17 11:01:04 -0600152
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600153 # store_true stores default value of False
154 parser.add_argument("--hr", action="store_true",
155 help="--hr is used to turn on hyperbolic routing")
Vince Lehman194be242015-10-15 18:01:42 -0500156
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600157 parser.add_argument("--list-experiments", action=PrintExperimentNames,
158 help="Lists the names of all available experiments")
Vince Lehman194be242015-10-15 18:01:42 -0500159
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600160 parser.add_argument("--no-cli", action="store_false", dest="isCliEnabled",
161 help="Run experiments and exit without showing the command line interface")
Vince Lehman194be242015-10-15 18:01:42 -0500162
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600163 parser.add_argument("--nPings", type=int, default=300,
164 help="Number of pings to perform between each node in the experiment")
Vince Lehman194be242015-10-15 18:01:42 -0500165
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600166 parser.add_argument("--nlsr-security", action="store_true", dest="nlsrSecurity",
167 help="Enables NLSR security")
Vince Lehman5d5a5662015-12-02 12:33:12 -0600168
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600169 parser.add_argument("-t", "--testbed", action="store_true", dest="testbed",
170 help="Instantiates a snapshot of the NDN Testbed irrespective of the tempfile provided")
ashuef3490b2015-02-17 11:01:04 -0600171
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600172 parser.add_argument("--work-dir", action="store", dest="workDir", default="/tmp",
173 help="Specify the working directory; default is /tmp")
Ashlesh Gawande1b663692015-10-14 16:38:10 -0500174
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600175 parser.add_argument("--result-dir", action="store", dest="resultDir", default=None,
176 help="Specify the full path destination folder where experiment results will be moved")
Ashlesh Gawanded829bfc2015-10-14 16:38:10 -0500177
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600178 parser.add_argument("--pct-traffic", dest="pctTraffic", type=float, default=1.0,
179 help="Specify the percentage of nodes each node should ping")
Ashlesh Gawanded9c9e522015-10-15 16:40:12 -0500180
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600181 parser.add_argument('--version', '-V', action='version', version='%(prog)s ' + VERSION_NUMBER,
182 help='Displays version information')
Vince Lehmane9f116d2015-07-15 10:40:21 -0500183
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600184 args = parser.parse_args()
ashuef3490b2015-02-17 11:01:04 -0600185
Vince Lehman194be242015-10-15 18:01:42 -0500186 options = ProgramOptions()
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600187 options.templateFile = args.tempfile
Vince Lehman194be242015-10-15 18:01:42 -0500188 options.ctime = args.ctime
189 options.experimentName = args.experiment
190 options.nFaces = args.faces
191 options.hr = args.hr
192 options.isCliEnabled = args.isCliEnabled
Vince Lehman5d5a5662015-12-02 12:33:12 -0600193 options.nlsrSecurity = args.nlsrSecurity
Vince Lehman194be242015-10-15 18:01:42 -0500194 options.nPings = args.nPings
Vince Lehman5d5a5662015-12-02 12:33:12 -0600195
Vince Lehman194be242015-10-15 18:01:42 -0500196 options.testbed = args.testbed
Ashlesh Gawande1b663692015-10-14 16:38:10 -0500197 options.workDir = args.workDir
Ashlesh Gawanded829bfc2015-10-14 16:38:10 -0500198 options.resultDir = args.resultDir
Ashlesh Gawanded9c9e522015-10-15 16:40:12 -0500199 options.pctTraffic = args.pctTraffic
ashuef3490b2015-02-17 11:01:04 -0600200
Vince Lehman194be242015-10-15 18:01:42 -0500201 if options.experimentName is not None and options.experimentName not in ExperimentManager.getExperimentNames():
202 print("No experiment named %s" % options.experimentName)
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600203 sys.exit(1)
Vince Lehman3b8bc652015-06-18 15:01:47 -0500204
Ashlesh Gawanded829bfc2015-10-14 16:38:10 -0500205 if options.experimentName is not None and options.resultDir is None:
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600206 print("No results folder specified; experiment results will remain in the working directory")
ashuef3490b2015-02-17 11:01:04 -0600207
Vince Lehman194be242015-10-15 18:01:42 -0500208 return options
ashuef3490b2015-02-17 11:01:04 -0600209
210class NdnTopo(Topo):
Ashlesh Gawande1b663692015-10-14 16:38:10 -0500211 def __init__(self, conf_arq, workDir, **opts):
ashuef3490b2015-02-17 11:01:04 -0600212 Topo.__init__(self, **opts)
213
214 global hosts_conf
215 global links_conf
216 hosts_conf = parse_hosts(conf_arq)
Vince Lehmanfbd47c92015-10-14 16:00:06 -0500217 switches_conf = parse_switches(conf_arq)
ashuef3490b2015-02-17 11:01:04 -0600218 links_conf = parse_links(conf_arq)
219
220 self.isTCLink = False
221 self.isLimited = False
222
223 for host in hosts_conf:
224 if host.cpu != None and self.isLimited != True:
225 self.isLimited = True
Ashlesh Gawande1b663692015-10-14 16:38:10 -0500226 self.addHost(host.name, app=host.app, params=host.uri_tuples, cpu=host.cpu,cores=host.cores,cache=host.cache, workdir=workDir)
ashuef3490b2015-02-17 11:01:04 -0600227
Vince Lehmanfbd47c92015-10-14 16:00:06 -0500228 for switch in switches_conf:
229 self.addSwitch(switch.name)
230
ashuef3490b2015-02-17 11:01:04 -0600231 for link in links_conf:
232 if len(link.linkDict) == 0:
233 self.addLink(link.h1, link.h2)
234 else:
235 self.addLink(link.h1, link.h2, **link.linkDict)
236 self.isTCLink = True
237
238 info('Parse of ' + conf_arq + ' done.\n')
239
Vince Lehman194be242015-10-15 18:01:42 -0500240def execute(options):
ashuef3490b2015-02-17 11:01:04 -0600241 "Create a network based on template_file"
242
Vince Lehman194be242015-10-15 18:01:42 -0500243 if options.testbed:
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600244 options.templateFile = INSTALL_DIR + 'minindn.testbed.conf'
ashuef3490b2015-02-17 11:01:04 -0600245
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600246 if os.path.exists(options.templateFile) == False:
247 info('Template file cannot be found. Exiting...\n')
248 sys.exit(1)
Ashlesh Gawande20f70762015-06-17 15:18:19 -0500249
Yucheng Zhang14aa5962016-04-05 17:10:15 -0500250 # Use nfd.conf as default configuration for NFD, else use the sample
251
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600252 nfdConfFile = "%s/nfd.conf" % INSTALL_DIR
Yucheng Zhang14aa5962016-04-05 17:10:15 -0500253 if os.path.isfile("/usr/local/etc/ndn/nfd.conf") == True:
254 shutil.copy2("/usr/local/etc/ndn/nfd.conf", nfdConfFile)
255 elif os.path.isfile("/usr/local/etc/ndn/nfd.conf.sample") == True:
256 shutil.copy2("/usr/local/etc/ndn/nfd.conf.sample", nfdConfFile)
257 else:
258 sys.exit("nfd.conf or nfd.conf.sample cannot be found in the expected directory. Exit.")
259
260 call(["sudo", "sed", "-i", 's|default_level [A-Z]*$|default_level $LOG_LEVEL|g', nfdConfFile])
Vince Lehman498625b2015-10-21 14:25:24 -0500261
Ashlesh Gawanded829bfc2015-10-14 16:38:10 -0500262 if options.resultDir is not None:
263 options.resultDir = createResultsDir(options.resultDir, options.nFaces, options.hr)
264
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600265 topo = NdnTopo(options.templateFile, options.workDir)
ashuef3490b2015-02-17 11:01:04 -0600266
267 t = datetime.datetime.now()
268
269 if topo.isTCLink == True and topo.isLimited == True:
270 net = Mininet(topo,host=CpuLimitedNdnHost,link=TCLink)
271 elif topo.isTCLink == True and topo.isLimited == False:
272 net = Mininet(topo,host=NdnHost,link=TCLink)
273 elif topo.isTCLink == False and topo.isLimited == True:
274 net = Mininet(topo,host=CpuLimitedNdnHost)
275 else:
276 net = Mininet(topo,host=NdnHost)
277
278 t2 = datetime.datetime.now()
279
280 delta = t2 - t
281
282 info('Setup time: ' + str(delta.seconds) + '\n')
283
284 net.start()
285
ashu2ad32e22015-05-29 13:37:40 -0500286 # Giving proper IPs to intf so neighbor nodes can communicate
287 # This is one way of giving connectivity, another way could be
288 # to insert a switch between each pair of neighbors
289 ndnNetBase = "1.0.0.0"
290 interfaces = []
291 for host in net.hosts:
292 for intf in host.intfList():
293 link = intf.link
294 node1, node2 = link.intf1.node, link.intf2.node
Vince Lehmanfbd47c92015-10-14 16:00:06 -0500295
296 if node1 in net.switches or node2 in net.switches:
Vince Lehmanfbd47c92015-10-14 16:00:06 -0500297 continue
298
ashu2ad32e22015-05-29 13:37:40 -0500299 if link.intf1 not in interfaces and link.intf2 not in interfaces:
300 interfaces.append(link.intf1)
301 interfaces.append(link.intf2)
302 node1.setIP(ipStr(ipParse(ndnNetBase) + 1) + '/30', intf=link.intf1)
303 node2.setIP(ipStr(ipParse(ndnNetBase) + 2) + '/30', intf=link.intf2)
304 ndnNetBase = ipStr(ipParse(ndnNetBase) + 4)
305
ashuef3490b2015-02-17 11:01:04 -0600306 nodes = "" # Used later to check prefix name in checkFIB
307
Vince Lehman5d5a5662015-12-02 12:33:12 -0600308 # NLSR Security
309 if options.nlsrSecurity is True:
310 Nlsr.createKeysAndCertificates(net, options.workDir)
311
ashuef3490b2015-02-17 11:01:04 -0600312 # NLSR initialization
313 for host in net.hosts:
314 nodes += str(host.name) + ","
315
316 conf = next(x for x in hosts_conf if x.name == host.name)
317 host.nlsrParameters = conf.nlsrParameters
318
Vince Lehman194be242015-10-15 18:01:42 -0500319 if options.nFaces is not None:
320 host.nlsrParameters["max-faces-per-prefix"] = options.nFaces
ashuef3490b2015-02-17 11:01:04 -0600321
Vince Lehman194be242015-10-15 18:01:42 -0500322 if options.hr is True:
ashuef3490b2015-02-17 11:01:04 -0600323 host.nlsrParameters["hyperbolic-state"] = "on"
324
325 # Generate NLSR configuration file
Vince Lehman5d5a5662015-12-02 12:33:12 -0600326 configGenerator = NlsrConfigGenerator(host, options.nlsrSecurity)
ashuef3490b2015-02-17 11:01:04 -0600327 configGenerator.createConfigFile()
328
329 # Start NLSR
ashu34c3ee02015-03-25 14:41:14 -0500330 host.nlsr = Nlsr(host)
331 host.nlsr.start()
ashuef3490b2015-02-17 11:01:04 -0600332
333 nodes = nodes[0:-1]
334
ashuef3490b2015-02-17 11:01:04 -0600335 for host in net.hosts:
336 if 'app' in host.params:
Ashlesh Gawande557cb842015-07-01 15:39:44 -0500337 if host.params['app'] != '':
338 app = host.params['app']
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600339 print("Starting " + app + " on node " + host.name)
Ashlesh Gawande557cb842015-07-01 15:39:44 -0500340 print(host.cmd(app))
ashuef3490b2015-02-17 11:01:04 -0600341
Vince Lehman3b8bc652015-06-18 15:01:47 -0500342 # Load experiment
Vince Lehman194be242015-10-15 18:01:42 -0500343 experimentName = options.experimentName
344
Vince Lehman3b8bc652015-06-18 15:01:47 -0500345 if experimentName is not None:
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600346 print("Loading experiment: %s" % experimentName)
Vince Lehman3b8bc652015-06-18 15:01:47 -0500347
348 experimentArgs = {
349 "net": net,
350 "nodes": nodes,
Vince Lehman194be242015-10-15 18:01:42 -0500351 "ctime": options.ctime,
352 "nPings": options.nPings,
Ashlesh Gawandeb07ada82016-08-05 14:29:17 -0500353 "strategy": Nfd.STRATEGY_BEST_ROUTE,
Ashlesh Gawanded9c9e522015-10-15 16:40:12 -0500354 "pctTraffic": options.pctTraffic
Vince Lehman3b8bc652015-06-18 15:01:47 -0500355 }
356
357 experiment = ExperimentManager.create(experimentName, experimentArgs)
358
359 if experiment is not None:
360 experiment.start()
361 else:
Ashlesh Gawande044611d2016-12-21 14:24:49 -0600362 print("ERROR: Experiment '%s' does not exist" % experimentName)
Vince Lehman3b8bc652015-06-18 15:01:47 -0500363 return
364
Vince Lehman194be242015-10-15 18:01:42 -0500365 if options.isCliEnabled is True:
ashuef3490b2015-02-17 11:01:04 -0600366 CLI(net)
367
368 net.stop()
369
Ashlesh Gawanded829bfc2015-10-14 16:38:10 -0500370 if options.resultDir is not None:
371 print("Moving results to %s" % options.resultDir)
372 os.system("sudo mv /%s/* %s" % (options.workDir, options.resultDir))
373
Ashlesh Gawande3807c1b2016-08-05 16:27:02 -0500374def signal_handler(signal, frame):
375 print('Cleaning up...')
376 call(["nfd-stop"])
377 call(["sudo", "mn", "--clean"])
378 sys.exit(1)
379
ashuef3490b2015-02-17 11:01:04 -0600380if __name__ == '__main__':
Vince Lehman3b8bc652015-06-18 15:01:47 -0500381
ashuef3490b2015-02-17 11:01:04 -0600382 hosts_conf = []
383 links_conf = []
Vince Lehman194be242015-10-15 18:01:42 -0500384
Ashlesh Gawande3807c1b2016-08-05 16:27:02 -0500385 signal.signal(signal.SIGINT, signal_handler)
386
Vince Lehman194be242015-10-15 18:01:42 -0500387 options = parse_args()
ashuef3490b2015-02-17 11:01:04 -0600388
389 setLogLevel('info')
Vince Lehman194be242015-10-15 18:01:42 -0500390 execute(options)