81a23417976e42d75149616a98975836f2146e7b
[nepi.git] / examples / wireless_overlay.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 from nepi.core.design import ExperimentDescription, FactoriesProvider
5 from nepi.core.execute import ExperimentController
6 from optparse import OptionParser, SUPPRESS_HELP
7 from nepi.util import proxy
8 from nepi.util.constants import DeploymentConfiguration as DC, ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP
9 import test_util
10 import os
11 import shutil
12 import tempfile
13 import itertools
14 import time
15 import math
16
17 """
18
19    ___________________________________________________________
20   |   NETNS                                                   |
21   |                                                           |
22   |                    __________                             | 
23   |                   |netns_node|  VLC_SERVER                |
24   |                   |__________|                            |
25   |                                                           |
26   |                       1/30                                |
27   |                    -----------                            |
28   |_________________________|_________________________________|
29                             |  
30                             |  0/30
31    _________________________|_________________________________
32   |    PL1                  |                                 |  
33   |                   ------------                            |
34   |                       2/30                                |
35   |                                                           |
36   |                       5/30                                |
37   |                  -------------                            |
38   |_________________________|_________________________________|
39                             | 
40                             |   4/30
41    _________________________|_________________________________
42   |    NS-3                 |                                 |
43   |                     --------                              |  
44   |                       6/30                                |
45   |                      ______                               |
46   |                     |  AP  |                              |
47   |                     |______|                              |
48   |                       33/27                               |
49   |                     --------                              |
50   |                       ((*))                               |
51   |                                                           |
52   |                                                           |
53   |     ((*))       ((*))       ((*))     ((*))               |         
54   |    -------     --------    -------   -------              |          
55   |     34/27       35/27      36/27      37/27               |          
56   |    +-----+     +-----+    +-----+    +-----+              |
57   |    |sta0 |     |sta1 |    |sta2 |    |sta3 |              |
58   |    +-----+     +-----+    +-----+    +-----+              |
59   |     66/30       70/30      74/30      78/30               |
60   |    -------     -------    -------    -------              |
61   |_______|___________|__________|__________|_________________|
62           |           |          |          |
63     ______|____   ____|____   ___|____   ___|____
64    | PL2  |    | |PL3 |    | |PL4|    | |PL5|    | 
65    |      |    | |    |    | |   |    | |   |    |
66    | -------   | | ------- | | ------ | |------- |  
67    |  65/30    | |  69/30  | | 73/30  | | 77/30  |                                 
68    |___________| |_________| |________| |________|
69
70 """
71
72 class WirelessOverlay(object):
73     def __init__(self):
74         usage = "usage: %prog -n number_sta -m movie -u user"
75         parser = OptionParser(usage=usage)
76         parser.add_option("-u", "--user", dest="user", help="Valid linux system user (not root).", type="str")
77         parser.add_option("-m", "--movie", dest="movie", help="Path to movie file to play", type="str")
78         parser.add_option("-n", "--nsta", dest="nsta", help="Number of wifi stations", type="int")
79         parser.add_option("-a", "--base_addr", dest="base_addr", help="Base address segment for the experiment", type="str")
80         parser.add_option("-s", "--slicename", dest="slicename", help="PlanetLab slice", type="str")
81         (options, args) = parser.parse_args()
82         if not options.movie:
83             parser.error("Missing 'movie' option.")
84         if options.user == 'root':
85             parser.error("Missing or invalid 'user' option.")
86         if options.user == 'root':
87             parser.error("Missing or invalid 'user' option.")
88         if options.nsta and options.nsta > 8:
89             parser.error("Try a number of stations under 9.")
90
91         self.user = options.user if options.user else os.getlogin()
92         self.movie = options.movie
93         self.nsta = options.nsta if options.nsta else 3
94         self.slicename = options.slicename if options.slicename else "inria_nepi3"
95         base = options.base_addr if options.base_addr else "192.168.4"
96         self.base_addr = base + ".%d"
97         self.root_dir = tempfile.mkdtemp()
98
99     def add_ip_address(self, iface, address, netprefix):
100         ip = iface.add_address()
101         ip.set_attribute_value("Address", address)
102         ip.set_attribute_value("NetPrefix", netprefix)
103
104     def add_route(self, node, destination, netprefix, nexthop):
105         route = node.add_route()
106         route.set_attribute_value("Destination", destination)
107         route.set_attribute_value("NetPrefix", netprefix)
108         route.set_attribute_value("NextHop", nexthop)
109
110     def add_ns3_fdnd(self, ns3_desc, node):
111         fdnd = ns3_desc.create("ns3::FdNetDevice")
112         node.connector("devs").connect(fdnd.connector("node"))
113         #fdnd.enable_trace("FdPcapTrace")
114         return fdnd
115
116     def add_ns3_node(self, ns3_desc):
117         node = ns3_desc.create("ns3::Node")
118         ipv4 = ns3_desc.create("ns3::Ipv4L3Protocol")
119         arp  = ns3_desc.create("ns3::ArpL3Protocol")
120         icmp = ns3_desc.create("ns3::Icmpv4L4Protocol")
121         udp = ns3_desc.create("ns3::UdpL4Protocol")
122         node.connector("protos").connect(ipv4.connector("node"))
123         node.connector("protos").connect(arp.connector("node"))
124         node.connector("protos").connect(icmp.connector("node"))
125         node.connector("protos").connect(udp.connector("node"))
126         return node
127
128     def add_ns3_wifi(self, ns3_desc, node, access_point = False):
129         wifi = ns3_desc.create("ns3::WifiNetDevice")
130         node.connector("devs").connect(wifi.connector("node"))
131
132         phy = ns3_desc.create("ns3::YansWifiPhy")
133         error = ns3_desc.create("ns3::NistErrorRateModel")
134         manager = ns3_desc.create("ns3::ArfWifiManager")
135         if access_point:
136             mac = ns3_desc.create("ns3::ApWifiMac")
137         else:
138             mac = ns3_desc.create("ns3::StaWifiMac")
139
140         phy.set_attribute_value("Standard", "WIFI_PHY_STANDARD_80211a")
141         mac.set_attribute_value("Standard", "WIFI_PHY_STANDARD_80211a")
142         phy.connector("err").connect(error.connector("phy"))
143         wifi.connector("phy").connect(phy.connector("dev"))
144         wifi.connector("mac").connect(mac.connector("dev"))
145         wifi.connector("manager").connect(manager.connector("dev"))
146
147         #phy.enable_trace("YansWifiPhyPcapTrace")
148         return wifi, phy
149
150     def add_ns3_constant_mobility(self, ns3_desc, node, x, y, z):
151         mobility = ns3_desc.create("ns3::ConstantPositionMobilityModel") 
152         position = "%d:%d:%d" % (x, y, z)
153         mobility.set_attribute_value("Position", position)
154         node.connector("mobility").connect(mobility.connector("node"))
155         return mobility
156
157     def add_ns3_wifi_channel(self, ns3_desc):
158         channel = ns3_desc.create("ns3::YansWifiChannel")
159         delay = ns3_desc.create("ns3::ConstantSpeedPropagationDelayModel")
160         loss  = ns3_desc.create("ns3::LogDistancePropagationLossModel")
161         channel.connector("delay").connect(delay.connector("chan"))
162         channel.connector("loss").connect(loss.connector("prev"))
163         return channel
164
165     def add_pl_testbed(self, exp_desc):
166         plchost = "nepiplc.pl.sophia.inria.fr"
167         port_base = 2000 + (os.getpid() % 1000) * 13
168         pl_ssh_key = os.environ.get(
169             "PL_SSH_KEY",
170             "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME'],) )
171         pl_user, pl_pwd = test_util.pl_auth()
172
173         pl_provider = FactoriesProvider("planetlab")
174         pl_desc = exp_desc.add_testbed_description(pl_provider)
175         pl_desc.set_attribute_value("homeDirectory", self.root_dir)
176         pl_desc.set_attribute_value("slice", self.slicename)
177         pl_desc.set_attribute_value("sliceSSHKey", pl_ssh_key)
178         pl_desc.set_attribute_value("authUser", pl_user)
179         pl_desc.set_attribute_value("authPass", pl_pwd)
180         pl_desc.set_attribute_value("plcHost", plchost)
181         pl_desc.set_attribute_value("tapPortBase", port_base)
182         pl_desc.set_attribute_value("p2pDeployment", False) # it's interactive, we don't want it in tests
183         #pl_desc.set_attribute_value("dedicatedSlice", True)
184         pl_desc.set_attribute_value("plLogLevel", "DEBUG")
185         return pl_desc
186
187     def add_pl_node(self, pl_desc, inet, label_prefix):
188         node = pl_desc.create("Node")
189         node.set_attribute_value("label", label_prefix)
190         iface = pl_desc.create("NodeInterface")
191         iface.set_attribute_value("label", label_prefix+"iface")
192         iface.connector("inet").connect(inet.connector("devs"))
193         node.connector("devs").connect(iface.connector("node"))
194         forwarder = pl_desc.create("MulticastForwarder")
195         node.connector("apps").connect(forwarder.connector("node"))
196         return node, iface
197
198     def add_ns3_in_pl(self, exp_desc, pl_desc, pl_node, pl_iface, root):
199         # Add NS3 support in node
200         plnepi = pl_desc.create("NepiDependency")
201         plns3 = pl_desc.create("NS3Dependency")
202         plnepi.connector("node").connect(pl_node.connector("deps"))
203         plns3.connector("node").connect(pl_node.connector("deps"))
204
205         # Create NS3 testbed running in pl_node
206         ns3_provider = FactoriesProvider("ns3")
207         ns3_desc = exp_desc.add_testbed_description(ns3_provider)
208         ns3_desc.set_attribute_value("rootDirectory", root)
209         ns3_desc.set_attribute_value("SimulatorImplementationType", "ns3::RealtimeSimulatorImpl")
210         ns3_desc.set_attribute_value("ChecksumEnabled", True)
211         ns3_desc.set_attribute_value(DC.DEPLOYMENT_HOST, "{#[%s].addr[0].[Address]#}" % (
212             pl_iface.get_attribute_value("label"),))
213         ns3_desc.set_attribute_value(DC.DEPLOYMENT_USER, 
214             pl_desc.get_attribute_value("slice"))
215         ns3_desc.set_attribute_value(DC.DEPLOYMENT_KEY, 
216             pl_desc.get_attribute_value("sliceSSHKey"))
217         ns3_desc.set_attribute_value(DC.DEPLOYMENT_MODE, DC.MODE_DAEMON)
218         ns3_desc.set_attribute_value(DC.DEPLOYMENT_COMMUNICATION, DC.ACCESS_SSH)
219         ns3_desc.set_attribute_value(DC.DEPLOYMENT_ENVIRONMENT_SETUP,
220             "{#[%s].[%s]#}" % (
221                 pl_node.get_attribute_value("label"),
222                 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,))
223         ns3_desc.set_attribute_value(DC.LOG_LEVEL, DC.DEBUG_LEVEL)
224         return ns3_desc
225
226     def add_netns_testbed(self, exp_desc):
227         netns_provider = FactoriesProvider("netns")
228         netns_desc = exp_desc.add_testbed_description(netns_provider)
229         netns_desc.set_attribute_value("homeDirectory", self.root_dir)
230         netns_desc.set_attribute_value(DC.DEPLOYMENT_MODE, DC.MODE_DAEMON)
231         netns_root_dir = os.path.join(self.root_dir, "netns")
232         os.mkdir(netns_root_dir)
233         netns_desc.set_attribute_value(DC.ROOT_DIRECTORY, netns_root_dir)
234         netns_desc.set_attribute_value(DC.LOG_LEVEL, DC.DEBUG_LEVEL)
235         netns_desc.set_attribute_value(DC.USE_SUDO, True)
236         return netns_desc
237
238     def add_netns_node(self, netns_desc):
239         node = netns_desc.create("Node")
240         node.set_attribute_value("forward_X11", True)
241         return node
242
243     def add_pl_ns3_connection(self, pl_desc, pl_node, pl_addr,
244             ns3_desc, ns3_node, ns3_addr):
245         pl_tap = pl_desc.create("TunInterface")
246         pl_tap.set_attribute_value("tun_cipher", "PLAIN") 
247         self.add_ip_address(pl_tap, pl_addr, 30)
248         pl_node.connector("devs").connect(pl_tap.connector("node"))
249         ns3_fdnd = ns3_desc.create("ns3::FdNetDevice")
250         ns3_node.connector("devs").connect(ns3_fdnd.connector("node"))
251         self.add_ip_address(ns3_fdnd, ns3_addr, 30)
252         pl_tap.connector("fd->").connect(ns3_fdnd.connector("->fd"))
253
254     def add_pl_ns3_tunchan_connection(self, pl_desc, pl_node, pl_addr,
255             ns3_desc, ns3_node, ns3_addr):
256         pl_tap = pl_desc.create("TunInterface")
257         self.add_ip_address(pl_tap, pl_addr, 30)
258         pl_node.connector("devs").connect(pl_tap.connector("node"))
259         ns3_fdnd = ns3_desc.create("ns3::FdNetDevice")
260         self.add_ip_address(ns3_fdnd, ns3_addr, 30)
261         ns3_node.connector("devs").connect(ns3_fdnd.connector("node"))
262         ns3_tc = ns3_desc.create("ns3::Nepi::TunChannel")
263         ns3_tc.connector("fd->").connect(ns3_fdnd.connector("->fd"))
264         pl_tap.connector("tcp").connect(ns3_tc.connector("tcp"))
265
266     def add_pl_netns_connection(self, pl_desc, pl_node, pl_addr,
267             netns_desc, netns_node, netns_addr):
268         pl_tap = pl_desc.create("TunInterface")
269         pl_tap.set_attribute_value("tun_cipher", "PLAIN") 
270         pl_tap.set_attribute_value("multicast", True) 
271         #pl_tap.enable_trace("pcap")
272         #pl_tap.enable_trace("packets")
273         self.add_ip_address(pl_tap, pl_addr, 30)
274         pl_node.connector("devs").connect(pl_tap.connector("node"))
275         netns_tap = netns_desc.create("TunNodeInterface")
276         netns_tap.set_attribute_value("up", True)
277         netns_tap.set_attribute_value("mtu", 1448)
278         self.add_ip_address(netns_tap, netns_addr, 30)
279         netns_node.connector("devs").connect(netns_tap.connector("node"))
280         netns_tunchannel = netns_desc.create("TunChannel")
281         netns_tunchannel.set_attribute_value("tun_cipher", "PLAIN") 
282         netns_tunchannel.connector("->fd").connect(netns_tap.connector("fd->"))
283         pl_tap.connector("tcp").connect(netns_tunchannel.connector("tcp"))
284
285     def run(self):
286         exp_desc = ExperimentDescription()
287
288         ## PL ##
289         pl_desc = self.add_pl_testbed(exp_desc)
290         pl_inet = pl_desc.create("Internet")
291         pl_node1, pl_iface1 = self.add_pl_node(pl_desc, pl_inet, 
292                 "node1_pl")
293
294         ## NETNS ##
295         netns_desc = self.add_netns_testbed(exp_desc)
296         netns_node = self.add_netns_node(netns_desc)
297
298         """
299         ## NS3 ##
300         ns3_desc = self.add_ns3_in_pl(exp_desc, pl_desc, pl_node1, pl_iface1, "ns3")
301         wifi_chan = self.add_ns3_wifi_channel(ns3_desc)
302         
303         # AP node
304         ap_node = self.add_ns3_node(ns3_desc)
305         self.add_ns3_constant_mobility(ns3_desc, ap_node, 0, 0, 0)
306         ap_wifi, ap_phy = self.add_ns3_wifi(ns3_desc, ap_node, access_point = True)
307         self.add_ip_address(ap_wifi, (self.base_addr%33), 27)
308         ap_phy.connector("chan").connect(wifi_chan.connector("phys"))
309
310         # wifi network 32/27
311         r = 50
312         # STA nodes
313         for i in xrange(0, self.nsta):
314             stai = self.add_ns3_node(ns3_desc)
315             angi = (360/self.nsta)*i
316             xi = r*math.cos(angi)
317             yi = r*math.sin(angi)
318             self.add_ns3_constant_mobility(ns3_desc, stai, xi, yi, 0)
319             wifi, phy= self.add_ns3_wifi(ns3_desc, stai, access_point = False)
320             wifi_addr = self.base_addr%(34 + i)
321             self.add_ip_address(wifi, wifi_addr, 27)
322             phy.connector("chan").connect(wifi_chan.connector("phys"))
323
324             self.add_route(stai, (self.base_addr%0), 30, (self.base_addr%33))
325             self.add_route(stai, (self.base_addr%4), 30, (self.base_addr%33))
326
327             net = 64 + i*4
328             pl_nodei, pl_ifacei = self.add_pl_node(pl_desc, pl_inet, 
329                     "node2%d_pl"%i)
330
331             pl_addr = (self.base_addr%(net+1))
332             ns3_addr = (self.base_addr%(net+2))
333             self.add_pl_ns3_tunchan_connection(pl_desc, pl_nodei, pl_addr,
334                 ns3_desc, stai, ns3_addr)
335             self.add_route(pl_nodei, (self.base_addr%32), 27, ns3_addr)
336             self.add_route(pl_nodei, (self.base_addr%0), 30, ns3_addr)
337             self.add_route(pl_nodei, (self.base_addr%4), 30, ns3_addr)
338
339             network = (self.base_addr%net)
340             self.add_route(netns_node, network, 30, (self.base_addr%2))
341             self.add_route(pl_node1, network, 30, (self.base_addr%6))
342             self.add_route(ap_node, network, 30, wifi_addr)
343         """ 
344         # connection PL1/NETNS
345         pl_addr = (self.base_addr%2)
346         netns_addr = (self.base_addr%1)
347         self.add_pl_netns_connection(pl_desc, pl_node1, pl_addr,
348             netns_desc, netns_node, netns_addr)
349         """
350         # connection PL1/NS3
351         pl_addr = (self.base_addr%5)
352         ns3_addr = (self.base_addr%6)
353         self.add_pl_ns3_connection(pl_desc, pl_node1, pl_addr,
354             ns3_desc, ap_node, ns3_addr)
355         """
356         # APPLICATIONS
357         command = "xterm" 
358         app = netns_desc.create("Application")
359         app.set_attribute_value("command", command)
360         app.set_attribute_value("user", self.user)
361         app.connector("node").connect(netns_node.connector("apps"))
362
363         """
364         # applications
365         #target = "{#[%s].addr[0].[Address]#}" % label
366         servers = []
367         clients = []
368         net = 0
369         target = self.base_addr%2
370         port = 5065
371         # vlc -vvv -I dummy /home/alina/repos/nepi/examples/big_buck_bunny_240p_mpeg4.ts --miface-addr '192.168.4.1'  --sout '#udp{dst=239.255.12.42}'
372         command = "sleep 2; vlc -I dummy %s --sout '#udp{dst=%s:%d}' vlc://quit" \
373             % (self.movie, target, port)
374         vlc_server = netns_desc.create("Application")
375         vlc_server.set_attribute_value("command", command)
376         vlc_server.set_attribute_value("user", self.user)
377         vlc_server.connector("node").connect(netns_node.connector("apps"))
378         servers.append(vlc_server.guid)
379
380         # vlc -vvv -I dummy udp://@239.255.12.42 --sout '#std{access=file,mux=ts,dst=coco.ts}'
381         command = "sudo dbus-uuidgen --ensure; vlc -vvv -I dummy udp://@%s:%d --sout '#std{access=file,mux=ts,dst=big_buck_bunny_stream.ts}' "  % (target, port)
382         vlc_client = pl_desc.create("Application")
383         vlc_client.set_attribute_value("buildDepends", "vlc")
384         vlc_client.set_attribute_value("rpmFusion", True)
385         vlc_client.set_attribute_value("command", command)
386         vlc_client.enable_trace("stdout")
387         vlc_client.enable_trace("stderr")
388         vlc_client.connector("node").connect(pl_node1.connector("apps"))
389         clients.append(vlc_client.guid)
390         """
391
392         """
393         # ROUTES
394         self.add_route(netns_node, (self.base_addr%32), 27, (self.base_addr%2))
395         self.add_route(netns_node, (self.base_addr%4), 30, (self.base_addr%2))
396         
397         self.add_route(pl_node1, (self.base_addr%32), 27, (self.base_addr%6))
398
399         self.add_route(ap_node, (self.base_addr%0), 30, (self.base_addr%5))
400         """
401         xml = exp_desc.to_xml()
402         controller = ExperimentController(xml, self.root_dir)
403         controller.start()
404         while not controller.is_finished(app.guid):
405             time.sleep(0.5)
406         time.sleep(0.1)
407         controller.stop()
408         controller.shutdown()
409
410         """
411         xml = exp_desc.to_xml()
412         controller = ExperimentController(xml, self.root_dir)
413         controller.start()
414         stop = False
415         while not stop:
416             time.sleep(0.5)
417             stop = True
418             for guid in clients:
419                 if not controller.is_finished(guid):
420                     stop = False
421         time.sleep(0.1)
422         controller.stop()
423         controller.shutdown()
424         """
425
426     def clean(self):
427         shutil.rmtree(self.root_dir)
428
429 if __name__ == '__main__':
430     example = WirelessOverlay()
431     example.run()
432     example.clean()
433