Removed version number from testbed file names. Only 1 version permitted at the time.
[nepi.git] / examples / roads09.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 os
10 import shutil
11 import tempfile
12 import time
13 import sys
14 import random
15
16 def pl_auth():
17     user = os.environ.get('PL_USER')
18     pwd = os.environ.get('PL_PASS')
19      
20     if user and pwd:
21         return (user,pwd)
22     else:
23         return None
24
25 class Roads09Ns3PLExample(object):
26     testbed_id = "planetlab"
27     slicename = "inria_nepi"
28     plchost = "nepiplc.pl.sophia.inria.fr"
29     
30     host1 = "nepi1.pl.sophia.inria.fr"
31     host2 = "nepi2.pl.sophia.inria.fr"
32     host3 = "nepi3.pl.sophia.inria.fr"
33     host4 = "nepi5.pl.sophia.inria.fr"
34     
35     def __init__(self):
36         #usage = "usage: %prog -m movie -u user"
37         #parser = OptionParser(usage=usage)
38         #parser.add_option("-u", "--user", dest="user", help="Valid linux system user (not root).", type="str")
39         #parser.add_option("-m", "--movie", dest="movie", help="Path to movie file to play", type="str")
40         #(options, args) = parser.parse_args()
41         #if not options.movie:
42         #    parser.error("Missing 'movie' option.")
43         #if options.user == 'root':
44         #    parser.error("Missing or invalid 'user' option.")
45
46         #self.user = options.user if options.user else os.getlogin()
47         #self.movie =  options.movie
48         
49         if not pl_auth():
50             print "Example requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)"
51             sys.exit(1)
52         
53         self.root_dir = tempfile.mkdtemp()
54
55
56     def make_experiment_desc(self):
57         testbed_id = self.testbed_id
58         slicename = self.slicename
59         plchost = self.plchost
60         pl_ssh_key = os.environ.get(
61             "PL_SSH_KEY",
62             "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME'],) )
63         pl_user, pl_pwd = pl_auth()
64
65         exp_desc = ExperimentDescription()
66         pl_provider = FactoriesProvider(testbed_id)
67         pl_desc = exp_desc.add_testbed_description(pl_provider)
68         pl_desc.set_attribute_value("homeDirectory", self.root_dir)
69         pl_desc.set_attribute_value("slice", slicename)
70         pl_desc.set_attribute_value("sliceSSHKey", pl_ssh_key)
71         pl_desc.set_attribute_value("authUser", pl_user)
72         pl_desc.set_attribute_value("authPass", pl_pwd)
73         pl_desc.set_attribute_value("plcHost", plchost)
74         
75         return pl_desc, exp_desc
76
77     def make_ns_in_pl(self, pl, exp, node1, iface1, root):
78         ns3_testbed_id = "ns3"
79         
80         # Add NS3 support in node1
81         plnepi = pl.create("NepiDependency")
82         plns3 = pl.create("NS3Dependency")
83         plnepi.connector("node").connect(node1.connector("deps"))
84         plns3.connector("node").connect(node1.connector("deps"))
85
86         # Create NS3 testbed running in node1
87         ns3_provider = FactoriesProvider(ns3_testbed_id)
88         ns3_desc = exp.add_testbed_description(ns3_provider)
89         ns3_desc.set_attribute_value("rootDirectory", root)
90         ns3_desc.set_attribute_value("SimulatorImplementationType", "ns3::RealtimeSimulatorImpl")
91         ns3_desc.set_attribute_value("ChecksumEnabled", True)
92         ns3_desc.set_attribute_value(DC.DEPLOYMENT_HOST, "{#[%s].addr[0].[Address]#}" % (
93             iface1.get_attribute_value("label"),))
94         ns3_desc.set_attribute_value(DC.DEPLOYMENT_USER, 
95             pl.get_attribute_value("slice"))
96         ns3_desc.set_attribute_value(DC.DEPLOYMENT_KEY, 
97             pl.get_attribute_value("sliceSSHKey"))
98         ns3_desc.set_attribute_value(DC.DEPLOYMENT_MODE, DC.MODE_DAEMON)
99         ns3_desc.set_attribute_value(DC.DEPLOYMENT_COMMUNICATION, DC.ACCESS_SSH)
100         ns3_desc.set_attribute_value(DC.DEPLOYMENT_ENVIRONMENT_SETUP,
101             "{#[%s].[%s]#}" % (
102                 node1.get_attribute_value("label"),
103                 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,))
104         ns3_desc.set_attribute_value(DC.LOG_LEVEL, DC.DEBUG_LEVEL)
105         
106         return ns3_desc
107     
108     
109
110     def add_ns3_fdnd(self, node, ns3_desc):
111         fdnd = ns3_desc.create("ns3::FileDescriptorNetDevice")
112         node.connector("devs").connect(fdnd.connector("node"))
113         fdnd.enable_trace("FileDescriptorPcapTrace")
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, node, ns3_desc, access_point = False, ip = None, prefix = 24):
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::QapWifiMac")
137         else:
138             mac = ns3_desc.create("ns3::QstaWifiMac")
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         if ip:
148             self.add_ip_address(wifi, ip, prefix)
149
150         phy.enable_trace("YansWifiPhyPcapTrace")
151         return wifi, phy
152
153     def add_ns3_random_mobility(self, node, ns3_desc, x, y, z, speed, 
154             bounds_width, bounds_height):
155         position = "%f:%f:%f" % (x, y, z)
156         bounds = "0|%f|0|%f" % (bounds_width, bounds_height) 
157         speed = "Constant:%f" % speed
158         mobility = ns3_desc.create("ns3::RandomDirection2dMobilityModel")
159         mobility.set_attribute_value("Position", position)
160         mobility.set_attribute_value("Bounds", bounds)
161         mobility.set_attribute_value("Speed", speed)
162         mobility.set_attribute_value("Pause",  "Constant:1")
163         node.connector("mobility").connect(mobility.connector("node"))
164         return mobility
165
166     def add_ns3_constant_mobility(self, node, ns3_desc, x, y, z):
167         mobility = ns3_desc.create("ns3::ConstantPositionMobilityModel") 
168         position = "%f:%f:%f" % (x, y, z)
169         mobility.set_attribute_value("Position", position)
170         node.connector("mobility").connect(mobility.connector("node"))
171         return mobility
172
173     def add_ns3_wifi_channel(self, ns3_desc):
174         channel = ns3_desc.create("ns3::YansWifiChannel")
175         delay = ns3_desc.create("ns3::ConstantSpeedPropagationDelayModel")
176         loss  = ns3_desc.create("ns3::LogDistancePropagationLossModel")
177         channel.connector("delay").connect(delay.connector("chan"))
178         channel.connector("loss").connect(loss.connector("prev"))
179         return channel
180
181     def add_ip_address(self, iface, address, prefix = 24):
182         ip = iface.add_address()
183         ip.set_attribute_value("Address", address)
184         ip.set_attribute_value("Broadcast", True)
185         ip.set_attribute_value("NetPrefix", prefix)
186         return ip
187
188     def add_route(self, nodes, destination, netprefix, nexthop):
189         for node in nodes:
190             route = node.add_route()
191             route.set_attribute_value("Destination", destination)
192             route.set_attribute_value("NetPrefix", netprefix)
193             route.set_attribute_value("NextHop", nexthop)
194
195     def make_pl_router(self, pl, hostname, label, ip, inet = None):
196         pl1 = pl.create("Node")
197         pl1.set_attribute_value("hostname", hostname)
198         pl1.set_attribute_value("label", label)
199         pl1.set_attribute_value("emulation", True)
200         pl1if = pl.create("NodeInterface")
201         pl1if.set_attribute_value("label", label+"if")
202         pl1tap = pl.create("TapInterface")
203         pl1tap.enable_trace("packets") # for error output
204         pl1tap.set_attribute_value("label", label+"tap")
205         pl1tap.set_attribute_value("snat", False)
206         inet = inet or pl.create("Internet")
207         pl1.connector("devs").connect(pl1if.connector("node"))
208         pl1.connector("devs").connect(pl1tap.connector("node"))
209         pl1if.connector("inet").connect(inet.connector("devs"))
210         
211         pl1tapip = pl1tap.add_address()
212         pl1tapip.set_attribute_value("Address", ip)
213         pl1tapip.set_attribute_value("NetPrefix", 24)
214         pl1tapip.set_attribute_value("Broadcast", False)
215         
216         return pl1, pl1if, pl1tap, pl1tapip, inet
217     
218     def make_mesh(self, pl, exp, inet):
219         scale = 1.0
220         walkdistance = 1.0
221         walkspeed = 0.1
222     
223         # Router 1 & NS3 host in PL
224         pl1, pl1if, pl1tap, pl1tapip, inet = self.make_pl_router(pl, 
225             self.host1, "mesh_pl1", "192.168.2.2", inet)
226
227         # Add NS3 support in pl1
228         ns3 = self.make_ns_in_pl(pl, exp, pl1, pl1if, "tb-ns3-roads09-1")
229         
230         # Add WiFi channel
231         chan = self.add_ns3_wifi_channel(ns3)
232         
233         # Add APs
234         ap1 = self.add_ns3_node(ns3)
235         ap2 = self.add_ns3_node(ns3)
236         ap3 = self.add_ns3_node(ns3)
237         ap4 = self.add_ns3_node(ns3)
238         ap1wifi, ap1phy = self.add_ns3_wifi(ap1, ns3, False, "192.168.2.3", 26)
239         ap2wifi, ap2phy = self.add_ns3_wifi(ap2, ns3, True, "192.168.2.4", 26)
240         ap3wifi, ap3phy = self.add_ns3_wifi(ap3, ns3, False, "192.168.2.5", 26)
241         ap4wifi, ap4phy = self.add_ns3_wifi(ap4, ns3, False, "192.168.2.6", 26)
242         self.add_ns3_constant_mobility(ap1, ns3, -scale, -scale, 0.0)
243         self.add_ns3_constant_mobility(ap2, ns3, +scale, -scale, 0.0)
244         self.add_ns3_constant_mobility(ap3, ns3, -scale, +scale, 0.0)
245         self.add_ns3_constant_mobility(ap4, ns3, +scale, +scale, 0.0)
246         
247         # Add WiFi nodes
248         wnode1 = self.add_ns3_node(ns3)        
249         wnode2 = self.add_ns3_node(ns3)        
250         wnode3 = self.add_ns3_node(ns3)        
251         wnode4 = self.add_ns3_node(ns3)        
252         wnode5 = self.add_ns3_node(ns3)        
253         wnode1wifi, wnode1phy = self.add_ns3_wifi(wnode1, ns3, False, "192.168.2.7", 26)
254         wnode2wifi, wnode2phy = self.add_ns3_wifi(wnode2, ns3, False, "192.168.2.8", 26)
255         wnode3wifi, wnode3phy = self.add_ns3_wifi(wnode3, ns3, False, "192.168.2.9", 26)
256         wnode4wifi, wnode4phy = self.add_ns3_wifi(wnode4, ns3, False, "192.168.2.10", 26)
257         wnode5wifi, wnode5phy = self.add_ns3_wifi(wnode5, ns3, False, "192.168.2.11", 26)
258         self.add_ns3_random_mobility(wnode1, ns3, -2*scale, -2*scale, 0.0, 
259             walkspeed, walkdistance, walkdistance)
260         self.add_ns3_random_mobility(wnode2, ns3, -2*scale, +scale, 0.0, 
261             walkspeed, walkdistance, walkdistance)
262         self.add_ns3_random_mobility(wnode3, ns3, -scale, +2*scale, 0.0, 
263             walkspeed, walkdistance, walkdistance)
264         self.add_ns3_random_mobility(wnode4, ns3, +scale, +2*scale, 0.0, 
265             walkspeed, walkdistance, walkdistance)
266         self.add_ns3_random_mobility(wnode5, ns3, +2*scale, +2*scale, 0.0, 
267             walkspeed, walkdistance, walkdistance)
268         
269         # Connect all WiFi phys to the channel
270         ap1phy.connector("chan").connect(chan.connector("phys"))
271         ap2phy.connector("chan").connect(chan.connector("phys"))
272         ap3phy.connector("chan").connect(chan.connector("phys"))
273         ap4phy.connector("chan").connect(chan.connector("phys"))
274         wnode1phy.connector("chan").connect(chan.connector("phys"))
275         wnode2phy.connector("chan").connect(chan.connector("phys"))
276         wnode3phy.connector("chan").connect(chan.connector("phys"))
277         wnode4phy.connector("chan").connect(chan.connector("phys"))
278         wnode5phy.connector("chan").connect(chan.connector("phys"))
279         
280         # Add inet connection to AP
281         ap2fdnd = self.add_ns3_fdnd(ap2, ns3)
282         ap2fdndip = self.add_ip_address(ap2fdnd, "192.168.2.20")
283         ap2fdndip.set_attribute_value("NetPrefix", 32) # p2p
284         pl1tap.connector("fd->").connect(ap2fdnd.connector("->fd"))
285         pl1tap.set_attribute_value("pointopoint", "192.168.2.20")
286         r = ap2.add_route()
287         r.set_attribute_value("Destination", "192.168.2.2")
288         r.set_attribute_value("NetPrefix", 32)
289         r.set_attribute_value("NextHop", "192.168.2.20")
290         
291         # return mesh router
292         return (
293             pl1, pl1if, pl1tap, pl1tapip, 
294             (wnode1, wnode2, wnode3, wnode4, wnode5),
295             (wnode1wifi, wnode2wifi, wnode3wifi, wnode4wifi, wnode5wifi),
296             (ap1, ap2, ap3, ap4),
297             (ap1wifi, ap2wifi, ap3wifi, ap4wifi),
298             ns3,
299             inet,
300         )
301     
302     def make_wifi_hotspot(self, pl, exp, inet):
303         scale = 1.0
304         walkdistance = 1.0
305         walkspeed = 0.1
306     
307         # Router 1 & NS3 host in PL
308         pl1, pl1if, pl1tap, pl1tapip, inet = self.make_pl_router(pl, 
309             self.host2, "hs_pl1", "192.168.2.65", inet)
310
311         # Add NS3 support in pl1
312         ns3 = self.make_ns_in_pl(pl, exp, pl1, pl1if, "tb-ns3-roads09-2")
313         
314         # Add WiFi channel
315         chan = self.add_ns3_wifi_channel(ns3)
316         
317         # Add APs
318         ap1 = self.add_ns3_node(ns3)
319         ap1wifi, ap1phy = self.add_ns3_wifi(ap1, ns3, True, "192.168.2.66", 26)
320         self.add_ns3_constant_mobility(ap1, ns3, 0.0, 0.0, 0.0)
321         
322         # Add WiFi nodes
323         wnode1 = self.add_ns3_node(ns3)
324         wnode2 = self.add_ns3_node(ns3)
325         wnode3 = self.add_ns3_node(ns3)
326         wnode4 = self.add_ns3_node(ns3)
327         wnode1wifi, wnode1phy = self.add_ns3_wifi(wnode1, ns3, False, "192.168.2.67", 26)
328         wnode2wifi, wnode2phy = self.add_ns3_wifi(wnode2, ns3, False, "192.168.2.68", 26)
329         wnode3wifi, wnode3phy = self.add_ns3_wifi(wnode3, ns3, False, "192.168.2.69", 26)
330         wnode4wifi, wnode4phy = self.add_ns3_wifi(wnode4, ns3, False, "192.168.2.70", 26)
331         self.add_ns3_random_mobility(wnode1, ns3, +scale, -2*scale, 0.0, 
332             walkspeed, walkdistance, walkdistance)
333         self.add_ns3_random_mobility(wnode2, ns3, -scale, -2*scale, 0.0, 
334             walkspeed, walkdistance, walkdistance)
335         self.add_ns3_random_mobility(wnode3, ns3, -2*scale, +scale, 0.0, 
336             walkspeed, walkdistance, walkdistance)
337         self.add_ns3_random_mobility(wnode4, ns3, -2*scale, +2*scale, 0.0, 
338             walkspeed, walkdistance, walkdistance)
339         
340         # Connect all WiFi phys to the channel
341         ap1phy.connector("chan").connect(chan.connector("phys"))
342         wnode1phy.connector("chan").connect(chan.connector("phys"))
343         wnode2phy.connector("chan").connect(chan.connector("phys"))
344         wnode3phy.connector("chan").connect(chan.connector("phys"))
345         wnode4phy.connector("chan").connect(chan.connector("phys"))
346         
347         # Add inet connection to AP2
348         ap1fdnd = self.add_ns3_fdnd(ap1, ns3)
349         ap1fdndip = self.add_ip_address(ap1fdnd, "192.168.2.80")
350         ap1fdndip.set_attribute_value("NetPrefix", 32) # p2p
351         pl1tap.connector("fd->").connect(ap1fdnd.connector("->fd"))
352         pl1tap.set_attribute_value("pointopoint", "192.168.2.80")
353         r = ap1.add_route()
354         r.set_attribute_value("Destination", "192.168.2.65")
355         r.set_attribute_value("NetPrefix", 32)
356         r.set_attribute_value("NextHop", "192.168.2.80")
357         
358         # return mesh router
359         return (
360             pl1, pl1if, pl1tap, pl1tapip,
361             (wnode1, wnode2, wnode3, wnode4),
362             (wnode1wifi, wnode2wifi, wnode3wifi, wnode4wifi),
363             ap1, ap1wifi,
364             ns3,
365             inet,
366         )
367     
368     def run(self):
369         pl, exp = self.make_experiment_desc()
370     
371         pl1, pl1if, pl1tap, pl1tapip, \
372             mesh_nodes, mesh_node_ifs, \
373             mesh_aps, mesh_ap_ifs, \
374             mesh_ns, \
375             inet = self.make_mesh(pl, exp, None)
376         pl2, pl2if, pl2tap, pl2tapip, \
377             hs_nodes, hs_node_ifs, \
378             hs_ap, hs_apif, \
379             hs_ns, \
380             inet = self.make_wifi_hotspot(pl, exp, inet)
381         
382         # Connect the routers
383         pl1etap = pl.create("TapInterface")
384         pl1etap.enable_trace("packets") # for error output
385         pl1etap.set_attribute_value("label", "pl1etap")
386         pl1etapip = pl1etap.add_address()
387         pl1etapip.set_attribute_value("Address", "192.168.2.1")
388         pl1etapip.set_attribute_value("NetPrefix", 24)
389         pl1etapip.set_attribute_value("Broadcast", False)
390
391         pl2etap = pl.create("TapInterface")
392         pl2etap.enable_trace("packets") # for error output
393         pl2etap.set_attribute_value("label", "pl2etap")
394         pl2etapip = pl2etap.add_address()
395         pl2etapip.set_attribute_value("Address", "192.168.2.81")
396         pl2etapip.set_attribute_value("NetPrefix", 24)
397         pl2etapip.set_attribute_value("Broadcast", False)
398         
399         pl1etap.connector("node").connect(pl1.connector("devs"))
400         pl2etap.connector("node").connect(pl2.connector("devs"))
401         pl1etap.connector("udp").connect(pl2etap.connector("udp"))
402         pl1etap.set_attribute_value("pointopoint", "192.168.2.81")
403         pl2etap.set_attribute_value("pointopoint", "192.168.2.1")
404         
405         # Connect the networks
406         
407         # apn -> ap2 (n != 2)
408         for ap in mesh_aps[:1] + mesh_aps[2:]:
409             r = ap.add_route()
410             r.set_attribute_value("Destination", "192.168.2.64")
411             r.set_attribute_value("NetPrefix", 26)
412             r.set_attribute_value("NextHop", 
413                 mesh_ap_ifs[1].addresses[0].get_attribute_value("Address") )
414
415         for wn in mesh_nodes:
416             apif = mesh_ap_ifs[ random.randint(0,len(mesh_aps)-1) ]
417             r = wn.add_route()
418             r.set_attribute_value("Destination", "192.168.2.64")
419             r.set_attribute_value("NetPrefix", 26)
420             r.set_attribute_value("NextHop", 
421                 apif.addresses[0].get_attribute_value("Address"))
422
423         r = mesh_aps[1].add_route()
424         r.set_attribute_value("Destination", "192.168.2.64")
425         r.set_attribute_value("NetPrefix", 26)
426         r.set_attribute_value("NextHop", "192.168.2.2")
427         
428         r = pl1.add_route()
429         r.set_attribute_value("Destination", "192.168.2.64")
430         r.set_attribute_value("NetPrefix", 26)
431         r.set_attribute_value("NextHop", "192.168.2.81")
432
433         r = pl2.add_route()
434         r.set_attribute_value("Destination", "192.168.2.64")
435         r.set_attribute_value("NetPrefix", 26)
436         r.set_attribute_value("NextHop", "192.168.2.80")
437
438         for wn in hs_nodes:
439             r = wn.add_route()
440             r.set_attribute_value("Destination", "192.168.2.0")
441             r.set_attribute_value("NetPrefix", 26)
442             r.set_attribute_value("NextHop", "192.168.2.66")
443
444         r = hs_ap.add_route()
445         r.set_attribute_value("Destination", "192.168.2.0")
446         r.set_attribute_value("NetPrefix", 26)
447         r.set_attribute_value("NextHop", "192.168.2.65")
448         
449         r = pl2.add_route()
450         r.set_attribute_value("Destination", "192.168.2.0")
451         r.set_attribute_value("NetPrefix", 26)
452         r.set_attribute_value("NextHop", "192.168.2.1")
453
454         r = pl1.add_route()
455         r.set_attribute_value("Destination", "192.168.2.0")
456         r.set_attribute_value("NetPrefix", 26)
457         r.set_attribute_value("NextHop", "192.168.2.20")
458
459         # Add pinger app inside the mesh
460         hs_node_ifs[0].set_attribute_value("label", "hotspot_node_1if")
461         mesh_node_ifs[0].set_attribute_value("label", "mesh_node_1if")
462         ping = mesh_ns.create("ns3::V4Ping")
463         ping.set_attribute_value("Remote", "192.168.2.67") #"{#[hotspot_node_1if].addr[0].[Address]#}")
464         ping.set_attribute_value("StartTime", "0s")
465         ping.set_attribute_value("StopTime", "10s")
466         ping.connector("node").connect(mesh_aps[0].connector("apps"))
467
468         xml = exp.to_xml()
469         
470         print xml
471
472         try:
473             controller = ExperimentController(xml, self.root_dir)
474             controller.start()
475             
476             while not controller.is_finished(ping.guid):
477                 time.sleep(0.5)
478             
479             taptrace = controller.trace(pl.guid, pl1tap.guid, "packets")
480                 
481         finally:
482             controller.stop()
483             controller.shutdown()
484         
485         print "Pakcets at router:"
486         print taptrace
487
488     def clean(self):
489         shutil.rmtree(self.root_dir)
490
491 if __name__ == '__main__':
492     example = Roads09Ns3PLExample()
493     example.run()
494     example.clean()
495