d31a5da0726c7c13865c9e9919680bae40e4a89f
[nepi.git] / examples / Multicast / multicast_experiment.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 import getpass
5 import os
6 import os.path
7 import re
8 import sys
9 import shutil
10 import signal
11 import tempfile
12 import time
13 import struct
14 import socket
15 import operator
16 import ipaddr
17 import gzip
18 import random
19 import traceback
20 import math
21 import subprocess
22
23 sys.path.append(os.path.abspath("../../src"))
24
25 from nepi.core.design import ExperimentDescription, FactoriesProvider
26 from nepi.core.execute import ExperimentController
27 from nepi.util import proxy
28 from nepi.util.constants import DeploymentConfiguration as DC, ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP
29 from nepi.testbeds.planetlab import util as plutil
30 from optparse import OptionParser
31
32
33 class PlanetLabMulticastOverlay:
34     testbed_id = "planetlab"
35     slicename = "inria_nepi"
36     plchost = "www.planet-lab.eu"
37     plkey = os.environ.get(
38             "PL_SSH_KEY",
39             "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME'],) )
40     pluser = os.environ.get("PL_USER")
41     plpass = os.environ.get("PL_PASS")
42     vnet = "192.168.3.0"
43     user = os.getlogin()
44     
45     port_base = 2000 + (os.getpid() % 1000) * 13
46     
47     def setUp(self):
48         self.root_dir = tempfile.mkdtemp()
49         self.__class__.port_base = self.__class__.port_base + 100
50         
51         print "Using:"
52         print "\tDISPLAY:", os.environ['DISPLAY']
53         print "\tPLC:", self.plchost
54         print "\tUsername:", self.pluser
55         print "\tslice:", self.slicename
56
57         api = plutil.getAPI(self.pluser, self.plpass, hostname=self.plchost)
58         self.vnet = plutil.getVnet(api, self.slicename).split('/')[0].strip()
59
60         print "\tvnet:", self.vnet
61
62     def tearDown(self):
63         try:
64             shutil.rmtree(self.root_dir)
65         except:
66             # retry
67             time.sleep(0.1)
68             shutil.rmtree(self.root_dir)
69
70     def make_experiment_desc(self):
71         testbed_id = self.testbed_id
72         slicename = self.slicename
73         plchost = self.plchost
74         pl_ssh_key = self.plkey
75         pl_user = self.pluser
76         pl_pwd = self.plpass
77         
78         plroot_dir = os.path.join(self.root_dir,"pl")
79         if not os.path.exists(plroot_dir):
80             os.makedirs(plroot_dir)
81
82         exp_desc = ExperimentDescription()
83         pl_provider = FactoriesProvider(testbed_id)
84         pl_desc = exp_desc.add_testbed_description(pl_provider)
85         pl_desc.set_attribute_value(DC.ROOT_DIRECTORY, plroot_dir )
86         pl_desc.set_attribute_value("homeDirectory", self.root_dir)
87         pl_desc.set_attribute_value("slice", slicename)
88         pl_desc.set_attribute_value("sliceSSHKey", pl_ssh_key)
89         pl_desc.set_attribute_value("authUser", pl_user)
90         pl_desc.set_attribute_value("authPass", pl_pwd)
91         pl_desc.set_attribute_value("plcHost", plchost)
92         pl_desc.set_attribute_value("tapPortBase", self.port_base)
93         pl_desc.set_attribute_value("p2pDeployment", not self.no_p2p_deploy)
94         pl_desc.set_attribute_value("dedicatedSlice", True)
95         pl_desc.set_attribute_value("plLogLevel", "INFO")
96         
97         return pl_desc, exp_desc
98     
99     def make_pl_tapnode(self, pl, ip, inet = None, label = None, hostname = None, routes = None, mcast = False, mcastrouter = False, types = None):
100         if not isinstance(ip, list):
101             ips = [ip]
102         else:
103             ips = ip
104         node1 = pl.create("Node")
105         if label: 
106             node1.set_attribute_value("label", label)
107         if hostname: 
108             node1.set_attribute_value("hostname", hostname)
109         iface1 = pl.create("NodeInterface")
110         if label:
111             iface1.set_attribute_value("label", label+"iface")
112         if types is None:
113             types = ["TapInterface"] * len(ips)
114         tap1 = []
115         tap1ip = []
116         for i,(ip,devtype) in enumerate(zip(ips,types)):
117             _tap1 = pl.create(devtype)
118             _tap1.set_attribute_value("multicast", True)
119             _tap1.enable_trace("pcap") # for error output
120             if label:
121                 _tap1.set_attribute_value("label", label+"tap"+(str(i+1) if i else ""))
122         
123             _tap1ip = _tap1.add_address()
124             _tap1ip.set_attribute_value("Address", ip)
125             _tap1ip.set_attribute_value("NetPrefix", 32)
126             _tap1ip.set_attribute_value("Broadcast", False)
127         
128             node1.connector("devs").connect(_tap1.connector("node"))
129             
130             tap1.append(_tap1)
131             tap1ip.append(_tap1ip)
132             
133         inet = inet or pl.create("Internet")
134         node1.connector("devs").connect(iface1.connector("node"))
135         iface1.connector("inet").connect(inet.connector("devs"))
136         
137         for destip, destprefix, nexthop in routes:
138             r1 = node1.add_route()
139             r1.set_attribute_value("Destination", destip)
140             r1.set_attribute_value("NetPrefix", destprefix)
141             r1.set_attribute_value("NextHop", nexthop)
142         
143         if mcast:
144             fwd = pl.create("MulticastForwarder")
145             fwd.enable_trace("stderr")
146             fwd.connector("node").connect(node1.connector("apps"))
147             if mcastrouter:
148                 mrt = pl.create("MulticastRouter")
149                 mrt.connector("fwd").connect(fwd.connector("router"))
150                 mrt.enable_trace("stderr")
151                 
152         return node1, iface1, tap1, tap1ip, inet
153     
154     def add_vlc_base(self, pl, node):
155         app = pl.create("Application")
156         app.set_attribute_value("rpmFusion", True)
157         app.set_attribute_value("depends", "vlc")
158         app.set_attribute_value("command", "sudo -S dbus-uuidgen --ensure ; vlc --version")
159         app.enable_trace("stdout")
160         app.enable_trace("stderr")
161         node.connector("apps").connect(app.connector("node"))
162         return app
163     
164     def add_vlc_restreamer(self, pl, node):
165         hostname = node.get_attribute_value("hostname")
166         app = self.add_vlc_base(pl, node)
167         app.set_attribute_value("label","vlc_restreamer_%d" % (node.guid,))
168         app.set_attribute_value("command",
169             "sudo -S dbus-uuidgen --ensure ; "
170             "vlc -vvv -I dummy"
171             " udp/ts://@239.255.12.42"
172             " --sout '#std{access=http,mux=ts,dst="+hostname+":8080}'")
173         return app
174     
175     def add_vlc_dumper(self, pl, node, hostname=None, labelprefix = "vlc_dumper", precmd = "sleep 5 ; "):
176         app = self.add_vlc_base(pl, node)
177         mylabel = "%s_%d" % (labelprefix, node.guid,)
178         if hostname is None:
179             hostname = node.get_attribute_value("hostname")
180         app.set_attribute_value("label",mylabel)
181         app.set_attribute_value("command",
182             precmd+
183             "sudo -S dbus-uuidgen --ensure ; "
184             "vlc -vvv -I dummy"
185             " http://"+hostname+":8080"
186             " --sout '#std{access=file,mux=ts,dst={#["+mylabel+"].trace[output].[name]#}}'")
187         app.enable_trace("output")
188         return app
189     
190     def add_vlc_source(self, pl, node, iflabels):
191         app = self.add_vlc_base(pl, node)
192         app.set_attribute_value("label","vlc_source_%d" % (node.guid,))
193         app.set_attribute_value("sources", self.movie_source)
194         app.set_attribute_value("command",
195             "sudo -S dbus-uuidgen --ensure ; "
196             "vlc -vvv -I dummy "
197             +os.path.basename(self.movie_source)
198             +" --sout '#duplicate{"
199             +','.join([
200                 "dst=std{access=udp,dst=239.255.12.42,mux=ts,ttl=64,miface-addr={#[%s].addr[0].[Address]#}}" % (iflabel,)
201                 for iflabel in iflabels
202             ])
203             +"}'")
204         return app
205     
206     def add_net_monitor(self, pl, node):
207         app = pl.create("Application")
208         app.set_attribute_value("label","network_monitor_%d" % (node.guid,))
209         app.set_attribute_value("command", 
210             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""")
211         app.enable_trace("stdout")
212         node.connector("apps").connect(app.connector("node"))
213         return app
214     
215     def add_ip_address(self, iface, address, netprefix):
216         ip = iface.add_address()
217         ip.set_attribute_value("Address", address)
218         ip.set_attribute_value("NetPrefix", netprefix)
219
220     def add_route(self, node, destination, netprefix, nexthop):
221         route = node.add_route()
222         route.set_attribute_value("Destination", destination)
223         route.set_attribute_value("NetPrefix", netprefix)
224         route.set_attribute_value("NextHop", nexthop)
225
226     def make_ns_in_pl(self, pl, exp, node1, iface1, root):
227         ns3_testbed_id = "ns3"
228         
229         # Add NS3 support in node1
230         plnepi = pl.create("NepiDependency")
231         plns3 = pl.create("NS3Dependency")
232         plnepi.connector("node").connect(node1.connector("deps"))
233         plns3.connector("node").connect(node1.connector("deps"))
234
235         # Create NS3 testbed running in node1
236         ns3_provider = FactoriesProvider(ns3_testbed_id)
237         ns_desc = exp.add_testbed_description(ns3_provider)
238         ns_desc.set_attribute_value("rootDirectory", root)
239         ns_desc.set_attribute_value("SimulatorImplementationType", "ns3::RealtimeSimulatorImpl")
240         ns_desc.set_attribute_value("ChecksumEnabled", True)
241         ns_desc.set_attribute_value(DC.DEPLOYMENT_HOST, "{#[%s].addr[0].[Address]#}" % (
242             iface1.get_attribute_value("label"),))
243         ns_desc.set_attribute_value(DC.DEPLOYMENT_USER, 
244             pl.get_attribute_value("slice"))
245         ns_desc.set_attribute_value(DC.DEPLOYMENT_KEY, 
246             pl.get_attribute_value("sliceSSHKey"))
247         ns_desc.set_attribute_value(DC.DEPLOYMENT_MODE, DC.MODE_DAEMON)
248         ns_desc.set_attribute_value(DC.DEPLOYMENT_COMMUNICATION, DC.ACCESS_SSH)
249         ns_desc.set_attribute_value(DC.DEPLOYMENT_ENVIRONMENT_SETUP,
250             "{#[%s].[%s]#}" % (
251                 node1.get_attribute_value("label"),
252                 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,))
253         ns_desc.set_attribute_value(DC.LOG_LEVEL, DC.DEBUG_LEVEL)
254         
255         return ns_desc
256    
257     def add_ip_address(self, iface, address, netprefix, broadcast = False):
258         ip = iface.add_address()
259         ip.set_attribute_value("Address", address)
260         ip.set_attribute_value("NetPrefix", netprefix)
261         ip.set_attribute_value("Broadcast", broadcast)
262         return ip
263
264     def add_route(self, node, destination, netprefix, nexthop):
265         route = node.add_route()
266         route.set_attribute_value("Destination", destination)
267         route.set_attribute_value("NetPrefix", netprefix)
268         route.set_attribute_value("NextHop", nexthop)
269         return route
270
271     def add_ns_fdnd(self, ns_desc, node):
272         fdnd = ns_desc.create("ns3::FdNetDevice")
273         node.connector("devs").connect(fdnd.connector("node"))
274         #fdnd.enable_trace("FdPcapTrace")
275         return fdnd
276
277     def add_ns_node(self, ns_desc):
278         node = ns_desc.create("ns3::Node")
279         ipv4 = ns_desc.create("ns3::Ipv4L3Protocol")
280         arp  = ns_desc.create("ns3::ArpL3Protocol")
281         icmp = ns_desc.create("ns3::Icmpv4L4Protocol")
282         udp = ns_desc.create("ns3::UdpL4Protocol")
283         node.connector("protos").connect(ipv4.connector("node"))
284         node.connector("protos").connect(arp.connector("node"))
285         node.connector("protos").connect(icmp.connector("node"))
286         node.connector("protos").connect(udp.connector("node"))
287         return node
288
289     def add_ns_wifi_dev(self, ns_desc, node, access_point = False):
290         wifi = ns_desc.create("ns3::WifiNetDevice")
291         node.connector("devs").connect(wifi.connector("node"))
292
293         phy = ns_desc.create("ns3::YansWifiPhy")
294         error = ns_desc.create("ns3::NistErrorRateModel")
295         manager = ns_desc.create("ns3::ArfWifiManager")
296         if access_point:
297             mac = ns_desc.create("ns3::ApWifiMac")
298         else:
299             mac = ns_desc.create("ns3::StaWifiMac")
300
301         phy.set_attribute_value("Standard", "WIFI_PHY_STANDARD_80211a")
302         mac.set_attribute_value("Standard", "WIFI_PHY_STANDARD_80211a")
303         phy.connector("err").connect(error.connector("phy"))
304         wifi.connector("phy").connect(phy.connector("dev"))
305         wifi.connector("mac").connect(mac.connector("dev"))
306         wifi.connector("manager").connect(manager.connector("dev"))
307
308         #phy.enable_trace("YansWifiPhyPcapTrace")
309         return wifi, phy
310
311     def add_ns_constant_mobility(self, ns_desc, node, x, y, z):
312         mobility = ns_desc.create("ns3::ConstantPositionMobilityModel") 
313         position = "%d:%d:%d" % (x, y, z)
314         mobility.set_attribute_value("Position", position)
315         node.connector("mobility").connect(mobility.connector("node"))
316         return mobility
317
318     def add_ns_wifi_channel(self, ns_desc):
319         channel = ns_desc.create("ns3::YansWifiChannel")
320         delay = ns_desc.create("ns3::ConstantSpeedPropagationDelayModel")
321         loss  = ns_desc.create("ns3::LogDistancePropagationLossModel")
322         channel.connector("delay").connect(delay.connector("chan"))
323         channel.connector("loss").connect(loss.connector("prev"))
324         return channel
325
326     def make_netns_testbed(self, exp_desc):
327         netns_provider = FactoriesProvider("netns")
328         netns_desc = exp_desc.add_testbed_description(netns_provider)
329         netns_desc.set_attribute_value("homeDirectory", self.root_dir)
330         netns_desc.set_attribute_value(DC.DEPLOYMENT_MODE, DC.MODE_DAEMON)
331         netns_root_dir = os.path.join(self.root_dir, "netns")
332         os.mkdir(netns_root_dir)
333         netns_desc.set_attribute_value(DC.ROOT_DIRECTORY, netns_root_dir)
334         netns_desc.set_attribute_value(DC.LOG_LEVEL, DC.DEBUG_LEVEL)
335         netns_desc.set_attribute_value(DC.USE_SUDO, True)
336         return netns_desc
337
338     def add_netns_node(self, netns_desc, forwardX = True, label = None):
339         node = netns_desc.create("Node")
340         node.set_attribute_value("forward_X11", forwardX)
341         if label:
342             node.set_attribute_value("label", label)
343         return node
344     
345     def add_netns_app(self, netns_desc, command, node):
346         app = netns_desc.create("Application")
347         app.set_attribute_value("command", command)
348         app.set_attribute_value("user", self.user)
349         app.connector("node").connect(node.connector("apps"))
350         return app
351
352     def add_pl_netns_connection(self, 
353             pl_tap, 
354             netns_desc, netns_node, netns_addr, netns_prefix = 30,
355             taplabel = None):
356         pl_tap.set_attribute_value("tun_cipher", "PLAIN") 
357         pl_tap.set_attribute_value("multicast", True) 
358         #pl_tap.enable_trace("pcap")
359         #pl_tap.enable_trace("packets")
360         pl_tapip = pl_tap.addresses[0].get_attribute_value("Address")
361         netns_tap = netns_desc.create("TunNodeInterface")
362         netns_tap.set_attribute_value("up", True)
363         netns_tap.set_attribute_value("mtu", 1448)
364         self.add_ip_address(netns_tap, netns_addr, netns_prefix)
365         if taplabel:
366             netns_tap.set_attribute_value("label", taplabel)
367         netns_node.connector("devs").connect(netns_tap.connector("node"))
368         netns_tunchannel = netns_desc.create("TunChannel")
369         netns_tunchannel.set_attribute_value("tun_cipher", "PLAIN") 
370         netns_tunchannel.connector("->fd").connect(netns_tap.connector("fd->"))
371         pl_tap.connector("tcp").connect(netns_tunchannel.connector("tcp"))
372         
373         pl_tap.set_attribute_value("tun_cipher", "PLAIN") 
374         pl_tap.set_attribute_value("pointopoint", netns_addr)
375
376     def add_pl_ns_connection(self, pl_desc, pl_node, pl_addr,
377             ns, ns_node, ns_addr, prefix = 30,
378             fd = False):
379         pl_tap = pl_desc.create("TapInterface")
380         if fd:
381             pl_tap.set_attribute_value("tun_cipher", "PLAIN") 
382         self.add_ip_address(pl_tap, pl_addr, prefix)
383         pl_node.connector("devs").connect(pl_tap.connector("node"))
384         ns_fdnd = ns.create("ns3::FdNetDevice")
385         ns_node.connector("devs").connect(ns_fdnd.connector("node"))
386         self.add_ip_address(ns_fdnd, ns_addr, prefix)
387         
388         if fd:
389             pl_tap.connector("fd->").connect(ns_fdnd.connector("->fd"))
390         else:
391             tunchannel = ns.create("ns3::Nepi::TunChannel")
392             tunchannel.connector("fd->").connect(ns_fdnd.connector("->fd"))
393             pl_tap.connector("udp").connect(tunchannel.connector("udp"))
394
395     def make_pl_overlay(self, numnodes):
396         ns3_testbed_id = "ns3"
397         
398         pl, exp = self.make_experiment_desc()
399         
400         # We'll make a distribution spanning tree using prefix matching as a distance
401         api = plutil.getAPI(self.pluser, self.plpass, hostname=self.plchost)
402         nodes = plutil.getNodes(api, numnodes, operatingSystem = 'f12')
403         root = min(nodes, key=operator.attrgetter('hostname'))
404         links = list(plutil.getSpanningTree(nodes, root=root))
405         
406         for node in nodes:
407             node.vif_ips = set()
408             node.children = []
409             node.childips = set()
410         
411         # Build an explicit tree
412         for slave, master in links:
413             master.children.append(slave)
414         
415         # We have to assign IPs and routes.
416         # The IP will be assigned sequentially, depth-first.
417         # This will result in rather compact routing rules
418         nextip = [128-numnodes]
419         def traverse(traverse, node, parent=None, base=struct.unpack('!L',socket.inet_aton(self.vnet))[0]):
420             if nextip[0] >= 254:
421                 raise RuntimeError, "Too many IPs to assign!"
422             
423             node.vif_addr = base | (nextip[0])
424             nips = 1+len(node.children) # one vif per child, plus one for the parent
425             nextip[0] += nips
426             
427             for i in xrange(nips):
428                 node.vif_ips.add(node.vif_addr+i)
429
430             if parent:
431                 parent.childips.update(node.vif_ips)
432
433             for i,child in enumerate(node.children):
434                 traverse(traverse, child, node, base)
435                 
436             if parent:
437                 parent.childips.update(node.childips)
438                 
439         traverse(traverse, root)
440         
441         def printtree(printtree, node, indent=''):
442             print indent, '-', socket.inet_ntoa(struct.pack('!L',node.vif_addr)), '\t', node.country, node.city, node.site, '\t', node.hostname
443             for child in node.children:
444                 childips = map(ipaddr.IPAddress, child.childips)
445                 childnets = ipaddr.collapse_address_list(childips)
446                 cip = ipaddr.IPAddress(child.vif_addr)
447                 for cnet in childnets:
448                     print indent, '|- R', cnet, '->', cip
449                 printtree(printtree, child, indent+' | ')
450         printtree(printtree, root)
451
452         inet = pl.create("Internet")
453
454         ns_chosen = []
455         leaves = []
456
457         def maketree(maketree, node, parent=None, parentIp=None):
458             routes = []
459             ctaps = []
460             for i,child in enumerate(node.children):
461                 childips = map(ipaddr.IPAddress, child.childips)
462                 childnets = ipaddr.collapse_address_list(childips)
463                 cip = ipaddr.IPAddress(child.vif_addr)
464                 pip = ipaddr.IPAddress(node.vif_addr+1+i)
465                 for cnet in childnets:
466                     routes.append((cnet.ip.exploded, cnet.prefixlen, cip.exploded))
467                 ctaps.append( maketree(maketree, child, node, pip) )
468             if parentIp:
469                 routes.append((self.vnet,24,parentIp))
470             
471             if not parent:
472                 label = "root"
473             else:
474                 label = None
475                 
476             # NS node, first leaf
477             if not ns_chosen and not node.children:
478                 ns_chosen.append(True)
479                 label = "ns_root"
480                 
481             ips = [ ipaddr.IPAddress(node.vif_addr+i) for i in xrange(1+len(node.children)) ]
482             node1, iface1, tap1, tap1ip, _ = self.make_pl_tapnode(pl, ips, inet, 
483                 hostname = node.hostname,
484                 routes = routes,
485                 mcastrouter = bool(node.children),
486                 mcast = True,
487                 label = label,
488                 types = ( [ "TapInterface" ] * len(ips) if parent else [ "TunInterface" ] + [ "TapInterface" ] * (len(ips)-1) ) 
489                 )
490             
491             for tap, ctap in zip(tap1[1:], ctaps):
492                 tap.connector("udp").connect(ctap.connector("udp"))
493             
494             # Store leaves
495             if not node.children:
496                 leaves.append((node, node1))
497             
498             self.add_net_monitor(pl, node1)
499             self.add_vlc_dumper(pl, node1)
500             self.add_vlc_restreamer(pl, node1)
501             #if not parent:
502             #    taplabels = [
503             #        t.get_attribute_value("label")
504             #        for t in tap1[1:]
505             #    ]
506             #    self.add_vlc_source(pl, node1, taplabels)
507             
508             return tap1[0]
509         roottap = maketree(maketree, root)
510
511         vnet_i = int(ipaddr.IPAddress(self.vnet))
512
513         ## NS3 ##
514         pl_ns_root = exp.get_element_by_label("ns_root")
515         pl_ns_root_iface = exp.get_element_by_label("ns_rootiface")
516         ns = self.make_ns_in_pl(pl, exp, pl_ns_root, pl_ns_root_iface, "ns3")
517         wifi_chan = self.add_ns_wifi_channel(ns)
518
519         # AP node
520         ap_node = self.add_ns_node(ns)
521         self.add_ns_constant_mobility(ns, ap_node, 0, 0, 0)
522         ap_wifi, ap_phy = self.add_ns_wifi_dev(ns, ap_node, access_point = True)
523         ap_phy.connector("chan").connect(wifi_chan.connector("phys"))
524
525         # connect AP to PL
526         pl_addr = str(ipaddr.IPAddress(vnet_i | 254))
527         ns_addr = str(ipaddr.IPAddress(vnet_i | 253))
528         self.add_pl_ns_connection(pl, pl_ns_root, pl_addr, ns, ap_node, ns_addr, fd = True)
529
530         wifi_net_prefix = 32-int(math.floor(math.log(256-nextip[0]&0xff) / math.log(2)))
531         wifi_net = vnet_i | (256 - (1<<(32-wifi_net_prefix)))
532         
533         # AP ip
534         ap_addr = str(ipaddr.IPAddress(vnet_i | 253))
535         ap_addr_prefix = 32-int(math.ceil(math.log(self.nsta+3) / math.log(2)))
536         self.add_ip_address(ap_wifi, ap_addr, ap_addr_prefix)
537         
538         # route for PL->wifi
539         self.add_route(pl_ns_root, 
540             str(ipaddr.IPAddress(wifi_net)), wifi_net_prefix,
541             ap_addr)
542         
543         print "NS-3 AP\t%s/%s <--> PL AP %s" % (ap_addr, ap_addr_prefix, pl_addr)
544         print " |"
545        
546         nextpip = (vnet_i | 255) >> (32-ap_addr_prefix) << (32-ap_addr_prefix)
547         nextdip = vnet_i | 252
548         ap_net = nextpip - (1<<(32-ap_addr_prefix))
549         r = 50
550         # STA nodes
551         for i in xrange(self.nsta):
552             stai = self.add_ns_node(ns)
553             angi = (360/self.nsta)*i
554             xi = r*math.cos(angi)
555             yi = r*math.sin(angi)
556             self.add_ns_constant_mobility(ns, stai, xi, yi, 0)
557             wifi, phy = self.add_ns_wifi_dev(ns, stai, access_point = False)
558             phy.connector("chan").connect(wifi_chan.connector("phys"))
559             
560             wifi_addr = str(ipaddr.IPAddress(vnet_i | nextdip))
561             nextdip -= 1
562
563             nextpip -= 4
564             while nextpip & 3:
565                 nextpip -= 1
566             plns_net_i = nextpip
567             plns_net = str(ipaddr.IPAddress(plns_net_i))
568             pl_addr2 = str(ipaddr.IPAddress(plns_net_i | 1))
569             ns_addr2 = str(ipaddr.IPAddress(plns_net_i | 2))
570
571             # route from AP (after others)
572             print " | R %s/%s -> %s" % ( plns_net,30,ns_addr2 )
573             self.add_route(ap_node, plns_net, 30, wifi_addr)
574
575             print " +---\t(|) %16s/%s" % (wifi_addr,ap_addr_prefix)
576             print " |         %16s (ns3) <---> (pl) %16s/30" % (ns_addr2, pl_addr2)
577             print " |\t       \t\t                 <--  R %s/24" % (self.vnet, )
578             print " |\t       \t R %s/30 -> %s" % (plns_net, pl_addr2)
579             print " |\t       \t R %s <-- %s/24" % (ap_addr, plns_net)
580
581             self.add_ip_address(wifi, wifi_addr, ap_addr_prefix)
582             self.add_route(stai, plns_net, 30, pl_addr2)
583             self.add_route(stai, self.vnet, 24, ap_addr)
584             
585             pl_nodei, _, pl_ifacei, _, _ = self.make_pl_tapnode(pl, [], inet, 
586                 routes = [(self.vnet, 24, ns_addr2)],
587                 mcast = False,
588                 label = "ns_plnode_%d" % (i+1,)
589                 )
590  
591             self.add_pl_ns_connection(
592                 pl, pl_nodei, pl_addr2,
593                 ns, stai, ns_addr2,
594                 prefix = 30)
595             
596             self.add_vlc_dumper(pl, pl_nodei,
597                 hostname = pl_addr2,
598                 labelprefix = "vlc_dumper_ns",
599                 precmd = "sleep 15 ; ")
600
601             # Validate (post-fact to let the user see the diagram above)
602             if nextpip < wifi_net:
603                 raise RuntimeError, "Not enough IPs for wifi section"
604         
605         # route back to PL (after others)
606         print " | R %s/%s -> %s" % ( self.vnet,24,pl_addr )
607         self.add_route(ap_node, self.vnet, 24, pl_addr)
608
609
610         ## NETNS ##
611         netns_addr = str(ipaddr.IPAddress(vnet_i | 1))
612
613         root1 = exp.get_element_by_label("root")
614         netns = self.make_netns_testbed(exp)
615         netns_node = self.add_netns_node(netns)
616         netns_term = self.add_netns_app(netns, "xterm", netns_node)
617         if self.movie_source:
618             cmd = (
619                 "vlc -I dummy "
620                 +os.path.abspath(self.movie_source)
621                 +" --sout '#std{access=udp{ttl=64,miface-addr="+netns_addr+"},dst=239.255.12.42,mux=ts}'"
622             )
623         else:
624             cmd = self.movie_command % {
625                 "dst" : "std{access=udp{ttl=64,miface-addr="+netns_addr+"},dst=239.255.12.42,mux=ts}"
626             }
627         netns_vlc  = self.add_netns_app(netns, cmd, netns_node)
628         
629         # connection PL1/NETNS
630         self.add_pl_netns_connection(
631             roottap,
632             netns, netns_node, netns_addr,
633             24,
634             taplabel="netns_source")
635         self.add_route(netns_node, 
636             "0.0.0.0", 0, 
637             str(ipaddr.IPAddress(root.vif_addr)) )
638         
639         # pick random hostname to stream from
640         interactive_source_host = random.sample(leaves,1)[0][0].hostname
641
642         xml = exp.to_xml()
643         test_dir = "./results"
644         #sys.exit(1)
645
646         try:
647             controller = ExperimentController(xml, self.root_dir)
648             controller.start()
649             
650             # launch vlc client to monitor activity
651             time.sleep(5)
652             proc = subprocess.Popen([
653                 "vlc", "-I", "dummy", "http://%s:8080" % (interactive_source_host,)])
654             
655             print >>sys.stderr, "Close xterm to shut down or Ctrl+C"
656             try:
657                 while not controller.is_finished(netns_term.guid):
658                     time.sleep(5)
659             except KeyboardInterrupt:
660                 # ping netns
661                 try:
662                     controller.traces_info()
663                 except:
664                     pass
665                 try:
666                     controller.traces_info()
667                 except:
668                     pass
669             
670             # kill streamer
671             os.kill(proc.pid, signal.SIGTERM)
672             
673             # download results
674             traces_info = controller.traces_info()
675             for progress, (testbed_guid, guids) in enumerate(traces_info.iteritems()):
676                 for subprogress, (guid, traces) in enumerate(guids.iteritems()):
677                     for name, data in traces.iteritems():
678                         path = data["filepath"]
679                         elem = exp.get_element(guid)
680                         if elem is not None:
681                             label = elem.get_attribute_value("label")
682                             if label is not None:
683                                 path = "%s-%s" % (label,path)
684                         
685                         if not path:
686                             continue
687                         
688                         print >>sys.stderr, ("%.2f%% Downloading trace" % (progress + (subprogress * 1.0 / len(guids)) * 100.0 / len(traces_info))), path
689                         
690                         filepath = os.path.join(test_dir, path)
691                         
692                         try:
693                             trace = controller.trace(guid, name)
694                         except:
695                             traceback.print_exc(file=sys.stderr)
696                             continue
697                         try:
698                             if not os.path.exists(os.path.dirname(filepath)):
699                                 os.makedirs(os.path.dirname(filepath))
700                         except:
701                             traceback.print_exc(file=sys.stderr)
702                         
703                         try:
704                             if len(trace) >= 2**20:
705                                 # Bigger than 1M, compress
706                                 tracefile = gzip.GzipFile(filepath+".gz", "wb")
707                             else:
708                                 tracefile = open(filepath,"wb")
709                             try:
710                                 tracefile.write(trace)
711                             finally:
712                                 tracefile.close()
713                         except:
714                             traceback.print_exc(file=sys.stderr)
715         finally:
716             try:
717                 controller.stop()
718             except:
719                 traceback.print_exc()
720             try:
721                 controller.shutdown()
722             except:
723                 traceback.print_exc()
724
725
726 if __name__ == '__main__':
727     usage = "usage: %prog -m movie -u user"
728     parser = OptionParser(usage=usage)
729     parser.add_option("-u", "--user", dest="user", help="Valid linux system user (not root).", type="str")
730     parser.add_option("-U", "--pluser", dest="pluser", help="PlanetLab PLC username", type="str")
731     parser.add_option("-m", "--movie", dest="movie", help="Path to movie file to play", type="str")
732     parser.add_option("-n", "--nsta", dest="nsta", default=3, help="Number of wifi stations attached to the overlay", type="int")
733     parser.add_option("-N", "--nodes", dest="nodes", default=5, help="Number of overlay nodes", type="int")
734     parser.add_option("-s", "--slicename", dest="slicename", help="PlanetLab slice", type="str")
735     parser.add_option("-H", "--plchost", dest="plchost", help="PlanetLab's PLC hostname", type="str")
736     parser.add_option("-k", "--plkey", dest="plkey", help="Slice SSH key", type="str")
737     parser.add_option("-P", "--no-p2p", dest="nop2p", help="Disable peer-to-peer deployment. Not recommended for first deployment.", 
738         action="store_true", default=False)
739     (options, args) = parser.parse_args()
740     if options.user == 'root':
741         parser.error("Missing or invalid 'user' option.")
742
743     exp = PlanetLabMulticastOverlay()
744     if not options.movie or options.movie.startswith("/dev/"):
745         # use camera
746         if not options.movie:
747             options.movie = "/dev/video0"
748         exp.movie_source = None
749         exp.movie_command = (
750             "vlc -I dummy -vvv --color "
751             "v4l:///dev/video0:size=320x240:channel=0:adev=/dev/dsp:audio=0 "
752             "--sout '#transcode{vcodec=mpeg4,acodec=aac,vb=100,ab=16,venc=ffmpeg{keyint=80,hq=rd},deinterlace}:"
753             "%(dst)s'"
754         )
755     else:
756         exp.movie_source = options.movie
757     exp.no_p2p_deploy = options.nop2p
758     exp.nsta = options.nsta
759     if options.user:
760         exp.user = options.user
761     if options.plchost:
762         exp.plchost = options.plchost
763     if options.slicename:
764         exp.slicename = options.slicename
765     if options.plkey:
766         exp.plkey = options.plkey
767     if options.pluser:
768         exp.pluser = options.pluser
769     if not exp.plpass:
770         exp.plpass = getpass.getpass("Password for %s: " % (exp.pluser,))
771     
772     # Fix some distro's environment to work well with netns
773     if re.match(r"[^:]*:\d+$", os.environ['DISPLAY']):
774         os.environ['DISPLAY'] += '.0'
775     if not os.environ.get('XAUTHORITY'):
776         os.environ['XAUTHORITY'] = os.path.join(os.environ['HOME'], '.Xauthority')
777     
778     try:
779         exp.setUp()
780         exp.make_pl_overlay(options.nodes)
781     finally:
782         exp.tearDown()
783