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