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