2 # -*- coding: utf-8 -*-
5 from optparse import OptionParser
19 sys.path.append("../../src")
21 from nepi.core.design import ExperimentDescription, FactoriesProvider
22 from nepi.core.execute import ExperimentController
23 from nepi.util import proxy
24 from nepi.util.constants import DeploymentConfiguration as DC, ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP
25 from nepi.testbeds.planetlab import util as plutil
27 class PlanetLabMulticastOverlay:
28 testbed_id = "planetlab"
29 slicename = "inria_nepi12"
30 plchost = "www.planet-lab.eu"
31 plkey = os.environ.get(
33 "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME'],) )
34 pluser = os.environ.get("PL_USER")
35 plpass = os.environ.get("PL_PASS")
38 port_base = 2000 + (os.getpid() % 1000) * 13
41 self.root_dir = tempfile.mkdtemp()
42 self.__class__.port_base = self.__class__.port_base + 100
46 shutil.rmtree(self.root_dir)
50 shutil.rmtree(self.root_dir)
52 def make_experiment_desc(self):
53 testbed_id = self.testbed_id
54 slicename = self.slicename
55 plchost = self.plchost
56 pl_ssh_key = self.plkey
60 exp_desc = ExperimentDescription()
61 pl_provider = FactoriesProvider(testbed_id)
62 pl_desc = exp_desc.add_testbed_description(pl_provider)
63 pl_desc.set_attribute_value("homeDirectory", self.root_dir)
64 pl_desc.set_attribute_value("slice", slicename)
65 pl_desc.set_attribute_value("sliceSSHKey", pl_ssh_key)
66 pl_desc.set_attribute_value("authUser", pl_user)
67 pl_desc.set_attribute_value("authPass", pl_pwd)
68 pl_desc.set_attribute_value("plcHost", plchost)
69 pl_desc.set_attribute_value("tapPortBase", self.port_base)
70 pl_desc.set_attribute_value("p2pDeployment", True)
71 pl_desc.set_attribute_value("dedicatedSlice", True)
72 pl_desc.set_attribute_value("plLogLevel", "INFO")
74 netns_provider = FactoriesProvider("netns")
75 netns = exp_desc.add_testbed_description(netns_provider)
76 netns.set_attribute_value("homeDirectory", self.root_dir)
77 netns.set_attribute_value(DC.DEPLOYMENT_MODE, DC.MODE_DAEMON)
78 netns_root_dir = os.path.join(self.root_dir, "netns")
79 os.mkdir(netns_root_dir)
80 netns.set_attribute_value(DC.ROOT_DIRECTORY, netns_root_dir)
81 netns.set_attribute_value(DC.LOG_LEVEL, DC.DEBUG_LEVEL)
82 netns.set_attribute_value(DC.USE_SUDO, True)
84 return pl_desc, netns, exp_desc
86 def make_pl_tapnode(self, pl, ip, inet = None, label = None, hostname = None, routes = None, mcast = False, mcastrouter = False):
87 if not isinstance(ip, list):
91 node1 = pl.create("Node")
93 node1.set_attribute_value("label", label)
95 node1.set_attribute_value("hostname", hostname)
96 iface1 = pl.create("NodeInterface")
98 iface1.set_attribute_value("label", label+"iface")
101 for i,ip in enumerate(ips):
102 _tap1 = pl.create("TapInterface")
103 _tap1.set_attribute_value("multicast", True)
104 _tap1.enable_trace("pcap") # for error output
106 _tap1.set_attribute_value("label", label+"tap"+(str(i+1) if i else ""))
108 _tap1ip = self.add_ip_address(_tap1, ip, 32)
110 node1.connector("devs").connect(_tap1.connector("node"))
113 tap1ip.append(_tap1ip)
115 inet = inet or pl.create("Internet")
116 node1.connector("devs").connect(iface1.connector("node"))
117 iface1.connector("inet").connect(inet.connector("devs"))
119 for destip, destprefix, nexthop in routes:
120 r1 = self.add_route(node1, destip, destprefix, nexthop)
123 fwd = pl.create("MulticastForwarder")
124 fwd.enable_trace("stderr")
125 fwd.connector("node").connect(node1.connector("apps"))
127 mrt = pl.create("MulticastRouter")
128 mrt.connector("fwd").connect(fwd.connector("router"))
129 mrt.enable_trace("stderr")
131 return node1, iface1, tap1, tap1ip, inet
133 def add_ip_address(self, iface, address, netprefix, broadcast = False):
134 ip = iface.add_address()
135 ip.set_attribute_value("Address", address)
136 ip.set_attribute_value("NetPrefix", netprefix)
137 ip.set_attribute_value("Broadcast", broadcast)
140 def add_route(self, node, destination, netprefix, nexthop):
141 route = node.add_route()
142 route.set_attribute_value("Destination", destination)
143 route.set_attribute_value("NetPrefix", netprefix)
144 route.set_attribute_value("NextHop", nexthop)
147 def add_vlc_base(self, pl, node):
148 app = pl.create("Application")
149 app.set_attribute_value("rpmFusion", True)
150 app.set_attribute_value("depends", "vlc")
151 app.set_attribute_value("command", "vlc --version")
152 app.enable_trace("stdout")
153 app.enable_trace("stderr")
154 node.connector("apps").connect(app.connector("node"))
157 def add_vlc_restreamer(self, pl, node):
158 hostname = node.get_attribute_value("hostname")
159 app = self.add_vlc_base(pl, node)
160 app.set_attribute_value("label","vlc_restreamer_%d" % (node.guid,))
161 app.set_attribute_value("command",
163 " udp://@239.255.12.42"
164 " --sout '#rtp{port=6060,sdp=rtsp://"+hostname+":8080/test.sdp}'")
167 def add_vlc_dumper(self, pl, node):
168 app = self.add_vlc_base(pl, node)
169 app.set_attribute_value("label","vlc_dumper_%d" % (node.guid,))
170 app.set_attribute_value("command",
172 " udp://@239.255.12.42"
174 app.enable_trace("output")
177 def add_vlc_source(self, netns, node, iflabel):
178 app = netns.create("Application")
179 app.set_attribute_value("user", self.user)
180 app.set_attribute_value("label","vlc_source_%d" % (node.guid,))
181 app.set_attribute_value("command",
183 +os.path.basename(self.movie_source)
184 +"--miface-addr {#[%s].addr[0].[Address]#} " % (iflabel,)
185 +"--sout '#udp{dst=239.255.12.42,ttl=64}'")
186 app.connector("node").connect(node.connector("apps"))
189 def add_net_monitor(self, pl, node):
190 app = pl.create("Application")
191 app.set_attribute_value("label","network_monitor_%d" % (node.guid,))
192 app.set_attribute_value("command",
193 r"""head -n 2 /proc/net/dev ; while true ; do cat /proc/net/dev | sed -r 's/.*/'"$(date -R)"': \0/' | grep eth0 ; sleep 1 ; done""")
194 app.enable_trace("stdout")
195 node.connector("apps").connect(app.connector("node"))
198 def make_ns_in_pl(self, pl, exp, node1, iface1, root):
199 ns3_testbed_id = "ns3"
201 # Add NS3 support in node1
202 plnepi = pl.create("NepiDependency")
203 plns3 = pl.create("NS3Dependency")
204 plnepi.connector("node").connect(node1.connector("deps"))
205 plns3.connector("node").connect(node1.connector("deps"))
207 # Create NS3 testbed running in node1
208 ns3_provider = FactoriesProvider(ns3_testbed_id)
209 ns = exp.add_testbed_description(ns3_provider)
210 ns.set_attribute_value("rootDirectory", root)
211 ns.set_attribute_value("SimulatorImplementationType", "ns3::RealtimeSimulatorImpl")
212 ns.set_attribute_value("ChecksumEnabled", True)
213 ns.set_attribute_value(DC.DEPLOYMENT_HOST, "{#[%s].addr[0].[Address]#}" % (
214 iface1.get_attribute_value("label"),))
215 ns.set_attribute_value(DC.DEPLOYMENT_USER,
216 pl.get_attribute_value("slice"))
217 ns.set_attribute_value(DC.DEPLOYMENT_KEY,
218 pl.get_attribute_value("sliceSSHKey"))
219 ns.set_attribute_value(DC.DEPLOYMENT_MODE, DC.MODE_DAEMON)
220 ns.set_attribute_value(DC.DEPLOYMENT_COMMUNICATION, DC.ACCESS_SSH)
221 ns.set_attribute_value(DC.DEPLOYMENT_ENVIRONMENT_SETUP,
223 node1.get_attribute_value("label"),
224 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,))
225 ns.set_attribute_value(DC.LOG_LEVEL, DC.DEBUG_LEVEL)
229 def add_pl_ns_node(self, pl_desc, inet, label_prefix):
230 node = pl_desc.create("Node")
231 node.set_attribute_value("label", label_prefix)
232 iface = pl_desc.create("NodeInterface")
233 iface.set_attribute_value("label", label_prefix+"iface")
234 iface.connector("inet").connect(inet.connector("devs"))
235 node.connector("devs").connect(iface.connector("node"))
236 forwarder = pl_desc.create("MulticastForwarder")
237 forwarder.enable_trace("stderr")
238 node.connector("apps").connect(forwarder.connector("node"))
241 def add_pl_ns_connection(self, pl_desc, pl_node, pl_addr,
242 ns, ns_node, ns_addr):
243 pl_tap = pl_desc.create("TapInterface")
244 pl_tap.set_attribute_value("tun_cipher", "PLAIN")
245 self.add_ip_address(pl_tap, pl_addr, 31)
246 pl_node.connector("devs").connect(pl_tap.connector("node"))
247 ns_fdnd = ns.create("ns3::FdNetDevice")
248 ns_node.connector("devs").connect(ns_fdnd.connector("node"))
249 self.add_ip_address(ns_fdnd, ns_addr, 31)
250 pl_tap.connector("fd->").connect(ns_fdnd.connector("->fd"))
252 def add_pl_ns_tunchan_connection(self, pl_desc, pl_node, pl_addr,
253 ns, ns_node, ns_addr):
254 pl_tap = pl_desc.create("TunInterface")
255 self.add_ip_address(pl_tap, pl_addr, 31)
256 pl_node.connector("devs").connect(pl_tap.connector("node"))
257 ns_fdnd = ns.create("ns3::FdNetDevice")
258 ns_fdnd.enable_trace("FdPcapTrace")
259 self.add_ip_address(ns_fdnd, ns_addr, 31)
260 ns_node.connector("devs").connect(ns_fdnd.connector("node"))
261 ns_tc = ns.create("ns3::Nepi::TunChannel")
262 ns_tc.connector("fd->").connect(ns_fdnd.connector("->fd"))
263 pl_tap.connector("tcp").connect(ns_tc.connector("tcp"))
265 def make_netns_node(self, netns):
266 node = netns.create("Node")
267 node.set_attribute_value("forward_X11", True)
269 app = netns.create("Application")
270 app.set_attribute_value("command", command)
271 app.set_attribute_value("user", self.user)
272 app.connector("node").connect(node.connector("apps"))
275 def make_pl_netns_connection(self, pl_desc, pl_node, netns,
276 netns_node, netns_iface_label):
277 base=struct.unpack('!L',socket.inet_aton(self.vnet))[0]
278 netns_addr = socket.inet_ntoa(struct.pack('!L',(base | 1)))
279 pl_addr = socket.inet_ntoa(struct.pack('!L',(base | 2)))
280 pl_tap = pl_desc.create("TunInterface")
281 pl_tap.set_attribute_value("multicast", True)
282 #pl_tap.set_attribute_value("tun_cipher", "PLAIN")
283 #pl_tap.enable_trace("pcap")
284 #pl_tap.enable_trace("packets")
285 self.add_ip_address(pl_tap, pl_addr, 31)
286 pl_node.connector("devs").connect(pl_tap.connector("node"))
288 netns_tap = netns.create("TunNodeInterface")
289 netns_tap.set_attribute_value("label", netns_iface_label)
290 netns_tap.set_attribute_value("up", True)
291 netns_tap.set_attribute_value("mtu", 1448)
292 self.add_ip_address(netns_tap, netns_addr, 31)
293 self.add_route(netns_node, self.vnet, 24, pl_addr)
294 netns_node.connector("devs").connect(netns_tap.connector("node"))
296 netns_tunchannel = netns.create("TunChannel")
297 #netns_tunchannel.set_attribute_value("tun_cipher", "PLAIN")
298 netns_tunchannel.connector("->fd").connect(netns_tap.connector("fd->"))
299 pl_tap.connector("tcp").connect(netns_tunchannel.connector("tcp"))
301 def add_ns_fdnd(self, ns, node):
302 fdnd = ns.create("ns3::FdNetDevice")
303 node.connector("devs").connect(fdnd.connector("node"))
304 #fdnd.enable_trace("FdPcapTrace")
307 def add_ns_node(self, ns):
308 node = ns.create("ns3::Node")
309 ipv4 = ns.create("ns3::Ipv4L3Protocol")
310 arp = ns.create("ns3::ArpL3Protocol")
311 icmp = ns.create("ns3::Icmpv4L4Protocol")
312 udp = ns.create("ns3::UdpL4Protocol")
313 node.connector("protos").connect(ipv4.connector("node"))
314 node.connector("protos").connect(arp.connector("node"))
315 node.connector("protos").connect(icmp.connector("node"))
316 node.connector("protos").connect(udp.connector("node"))
319 def add_ns_wifi_dev(self, ns, node, access_point = False):
320 wifi = ns.create("ns3::WifiNetDevice")
321 node.connector("devs").connect(wifi.connector("node"))
323 phy = ns.create("ns3::YansWifiPhy")
324 error = ns.create("ns3::NistErrorRateModel")
325 manager = ns.create("ns3::ArfWifiManager")
327 mac = ns.create("ns3::ApWifiMac")
329 mac = ns.create("ns3::StaWifiMac")
331 phy.set_attribute_value("Standard", "WIFI_PHY_STANDARD_80211a")
332 mac.set_attribute_value("Standard", "WIFI_PHY_STANDARD_80211a")
333 phy.connector("err").connect(error.connector("phy"))
334 wifi.connector("phy").connect(phy.connector("dev"))
335 wifi.connector("mac").connect(mac.connector("dev"))
336 wifi.connector("manager").connect(manager.connector("dev"))
338 #phy.enable_trace("YansWifiPhyPcapTrace")
341 def add_ns_constant_mobility(self, ns, node, x, y, z):
342 mobility = ns.create("ns3::ConstantPositionMobilityModel")
343 position = "%d:%d:%d" % (x, y, z)
344 mobility.set_attribute_value("Position", position)
345 node.connector("mobility").connect(mobility.connector("node"))
348 def add_ns_wifi_channel(self, ns):
349 channel = ns.create("ns3::YansWifiChannel")
350 delay = ns.create("ns3::ConstantSpeedPropagationDelayModel")
351 loss = ns.create("ns3::LogDistancePropagationLossModel")
352 channel.connector("delay").connect(delay.connector("chan"))
353 channel.connector("loss").connect(loss.connector("prev"))
356 def make_ns_wifi(self, ns, pl, pl_ns_root, inet, numwifinodes, nextip):
357 base=struct.unpack('!L',socket.inet_aton(self.vnet))[0]
359 for i in xrange(2, 6):
360 nr = int(math.pow(2, i))
361 if nr <= (numwifinodes + 2):
366 # how many IPs will we need?
367 # 1 for the AP, 2 for each station and one for each extra PL node
368 # BUT we need to also reserve IPs to sum up to a posible subnetwork
369 # number of nodes: 2, 4, 8, 16, etc ...
370 # And finally, we need 2 extra IPs for the PL-AP iface
372 nrips = (1 + 2*numwifinodes + nr + 2)
373 if nrips + nextip[0] > 255:
376 raise RuntimeError("There are not enough IP addresses for the wireless network", )
381 ip = socket.inet_ntoa(struct.pack('!L',(base | _nextwifiip[0])))
385 _nextnstapip = [(254 - nr -1)]
387 ip = socket.inet_ntoa(struct.pack('!L',(base | _nextnstapip[0])))
391 _nexttapip = [(254 - nr - 1 - numwifinodes)]
393 ip = socket.inet_ntoa(struct.pack('!L',(base | _nexttapip[0])))
398 wifi_chan = self.add_ns_wifi_channel(ns)
401 ap_node = self.add_ns_node(ns)
402 self.add_ns_constant_mobility(ns, ap_node, 0, 0, 0)
403 ap_wifi, ap_phy = self.add_ns_wifi_dev(ns, ap_node, access_point = True)
404 ap_phy.connector("chan").connect(wifi_chan.connector("phys"))
407 _nextplip = (254 - nrips)
408 pl_ip = socket.inet_ntoa(struct.pack('!L',(base | _nextplip)))
409 print "PL IP %s" % pl_ip
411 ns_ip = socket.inet_ntoa(struct.pack('!L',(base | _nextplip)))
412 print "NS IP %s" % ns_ip
413 self.add_pl_ns_connection(pl, pl_ns_root, pl_ip, ns, ap_node, ns_ip)
415 # routes in and out ns
416 self.add_route(ap_node, self.vnet, 24, pl_ip)
418 ip = socket.inet_ntoa(struct.pack('!L',(base | net)))
419 self.add_route(pl_ns_root, ip, netprefix, ns_ip)
422 print "AP IP %s" % ap_ip
423 self.add_ip_address(ap_wifi, ap_ip, netprefix)
427 for i in xrange(0, numwifinodes):
428 stai = self.add_ns_node(ns)
429 angi = (360/numwifinodes)*i
430 xi = r*math.cos(angi)
431 yi = r*math.sin(angi)
432 self.add_ns_constant_mobility(ns, stai, xi, yi, 0)
433 wifi, phy = self.add_ns_wifi_dev(ns, stai, access_point = False)
434 phy.connector("chan").connect(wifi_chan.connector("phys"))
436 wifi_ip = nextwifiip()
437 print "WIFI IP %s" % wifi_ip
438 self.add_ip_address(wifi, wifi_ip, netprefix)
439 self.add_route(stai, self.vnet, 24, ap_ip)
442 pl_nodei, pl_ifacei = self.add_pl_ns_node(pl, inet,
445 pl_addr = (self.base_addr%(net+1))
446 ns3_addr = (self.base_addr%(net+2))
447 self.add_pl_ns_tunchan_connection(pl_desc, pl_nodei, pl_addr,
449 self.add_route(pl_nodei, (self.base_addr%32), 27, ns3_addr)
450 self.add_route(pl_nodei, (self.base_addr%0), 30, ns3_addr)
451 self.add_route(pl_nodei, (self.base_addr%4), 30, ns3_addr)
453 network = (self.base_addr%net)
454 self.add_route(netns_node, network, 30, (self.base_addr%2))
455 self.add_route(pl_node1, network, 30, (self.base_addr%6))
456 self.add_route(ap_node, network, 30, wifi_addr)
459 def make_pl_overlay(self, numnodes, numwifinodes):
460 print "make_pl_overlay ..."
461 ns3_testbed_id = "ns3"
463 pl, netns, exp = self.make_experiment_desc()
464 # We'll make a distribution spanning tree using prefix matching as a distance
465 api = plutil.getAPI(self.pluser, self.plpass)
466 nodes = plutil.getNodes(api, numnodes, operatingSystem = 'f12')
467 root = min(nodes, key=operator.attrgetter('hostname'))
468 links = list(plutil.getSpanningTree(nodes, root=root))
473 node.childips = set()
475 # Build an explicit tree
476 for slave, master in links:
477 master.children.append(slave)
479 # We have to assign IPs and routes.
480 # The IP will be assigned sequentially, depth-first.
481 # This will result in rather compact routing rules
482 nextip = [128-numnodes]
483 def traverse(traverse, node, parent=None, base=struct.unpack('!L',socket.inet_aton(self.vnet))[0]):
485 raise RuntimeError, "Too many IPs to assign!"
487 node.vif_addr = base | (nextip[0])
488 nips = 1+len(node.children) # one vif per child, plus one for the parent
491 for i in xrange(nips):
492 node.vif_ips.add(node.vif_addr+i)
495 parent.childips.update(node.vif_ips)
497 for i,child in enumerate(node.children):
498 traverse(traverse, child, node, base)
501 parent.childips.update(node.childips)
504 traverse(traverse, root)
506 def printtree(printtree, node, indent=''):
507 print indent, '-', socket.inet_ntoa(struct.pack('!L',node.vif_addr)), node.country, node.city, node.site
508 for child in node.children:
509 childips = map(ipaddr.IPAddress, child.childips)
510 childnets = ipaddr.collapse_address_list(childips)
511 cip = ipaddr.IPAddress(child.vif_addr)
512 for cnet in childnets:
513 print indent, '|- R', cnet, '->', cip
514 printtree(printtree, child, indent+' | ')
515 printtree(printtree, root)
517 inet = pl.create("Internet")
521 def maketree(maketree, node, parent=None, parentIp=None):
524 for i,child in enumerate(node.children):
525 childips = map(ipaddr.IPAddress, child.childips)
526 childnets = ipaddr.collapse_address_list(childips)
527 cip = ipaddr.IPAddress(child.vif_addr)
528 pip = ipaddr.IPAddress(node.vif_addr+1+i)
529 for cnet in childnets:
530 routes.append((cnet.ip.exploded, cnet.prefixlen, cip.exploded))
531 ctaps.append( maketree(maketree, child, node, pip) )
534 routes.append((self.vnet,24,parentIp))
540 if not ns_chosen and node.children:
541 ns_chosen.append(True)
543 ips = [ ipaddr.IPAddress(node.vif_addr+i) for i in xrange(1+len(node.children)) ]
544 node1, iface1, tap1, tap1ip, _ = self.make_pl_tapnode(pl, ips, inet,
545 hostname = node.hostname,
547 mcastrouter = bool(node.children),
551 for tap, ctap in zip(tap1[1:], ctaps):
552 tap.connector("udp").connect(ctap.connector("udp"))
554 self.add_net_monitor(pl, node1)
555 self.add_vlc_restreamer(pl, node1)
556 if random.random() < 0.1 and parent:
557 self.add_vlc_dumper(pl, node1)
562 maketree(maketree, root)
564 # create a netns node and connect it to the root pl node
565 pl_root = exp.get_element_by_label("root")
566 netns_source = self.make_netns_node(netns)
567 iflabel = "source-iface"
568 self.make_pl_netns_connection(pl, pl_root, netns,
569 netns_source, iflabel)
570 self.add_vlc_source(netns, netns_source, iflabel)
572 # add ns wireless network
573 pl_ns_root = exp.get_element_by_label("ns_root")
574 pl_ns_root_iface = exp.get_element_by_label("ns_rootiface")
575 ns = self.make_ns_in_pl(pl, exp, pl_ns_root, pl_ns_root_iface, "ns3")
576 self.make_ns_wifi(ns, pl, pl_ns_root, inet, numwifinodes, nextip)
579 test_dir = "./results"
582 controller = ExperimentController(xml, self.root_dir)
585 print >>sys.stderr, "Press CTRL-C to shut down"
589 except KeyboardInterrupt:
593 for testbed_guid, guids in controller.traces_info().iteritems():
594 for guid, traces in guids.iteritems():
595 for name, data in traces.iteritems():
596 path = data["filepath"]
601 print >>sys.stderr, "Downloading trace", path
603 filepath = os.path.join(test_dir, path)
606 trace = controller.trace(guid, name)
608 traceback.print_exc(file=sys.stderr)
611 if not os.path.exists(os.path.dirname(filepath)):
612 os.makedirs(os.path.dirname(filepath))
614 traceback.print_exc(file=sys.stderr)
617 if len(trace) >= 2**20:
618 # Bigger than 1M, compress
619 tracefile = gzip.GzipFile(filepath+".gz", "wb")
621 tracefile = open(filepath,"wb")
623 tracefile.write(trace)
627 traceback.print_exc(file=sys.stderr)
633 traceback.print_exc()
635 controller.shutdown()
638 traceback.print_exc()
641 if __name__ == '__main__':
642 usage = "usage: %prog -n number_sta -m movie -u user"
643 parser = OptionParser(usage=usage)
644 parser.add_option("-u", "--user", dest="user", help="Valid linux system user (not root).", type="str", default=os.getlogin())
645 parser.add_option("-m", "--movie", dest="movie", help="Path to movie file to play", type="str")
646 parser.add_option("-n", "--nsta", dest="nsta", help="Number of wifi stations", type="int")
647 parser.add_option("-N", "--nodes", dest="nsta", help="Number of overlay nodes", type="int")
648 parser.add_option("-a", "--base_addr", dest="base_addr", help="Base address segment for the experiment", type="str")
649 parser.add_option("-s", "--slicename", dest="slicename", help="PlanetLab slice", type="str")
650 (options, args) = parser.parse_args()
651 if not options.movie:
652 parser.error("Missing 'movie' option.")
653 if options.user == 'root':
654 parser.error("Missing or invalid 'user' option.")
655 if options.nsta and options.nsta > 8:
656 parser.error("Try a number of stations under 9.")
658 exp = PlanetLabMulticastOverlay()
659 exp.movie_source = options.movie
660 exp.user = options.user
663 exp.make_pl_overlay(5, 2)