4 ## Experiment topology:
6 ## ccncatchunks ccnsendchunks
8 ## .-> node1 -- .. -- nodei -- .. -- nodeN <-.
11 ## - Nodes are connected through an overlay network over the Intenet
12 ## - On each node runs a CCNx daemon
13 ## - Static multicast entries are added to the CCNx FIB on each node to communicate them in series.
14 ## (Nodes only have FIB entries to at most two nodes)
18 from nepi.core.design import ExperimentDescription, FactoriesProvider
19 from nepi.core.execute import ExperimentController
20 from nepi.util.constants import ApplicationStatus as AS
23 from optparse import OptionParser, SUPPRESS_HELP
31 # Trak SIGTERM, and set global termination flag instead of dying
33 def _finalize(sig,frame):
35 TERMINATE.append(None)
36 signal.signal(signal.SIGTERM, _finalize)
37 signal.signal(signal.SIGINT, _finalize)
39 def create_slice_desc(slicename, plc_host, pl_user, pl_pwd, pl_ssh_key,
40 port_base, root_dir, exp_desc):
41 pl_provider = FactoriesProvider("planetlab")
42 slice_desc = exp_desc.add_testbed_description(pl_provider)
43 slice_desc.set_attribute_value("homeDirectory", root_dir)
44 slice_desc.set_attribute_value("slice", slicename)
45 slice_desc.set_attribute_value("sliceSSHKey", pl_ssh_key)
46 slice_desc.set_attribute_value("authUser", pl_user)
47 slice_desc.set_attribute_value("authPass", pl_pwd)
48 slice_desc.set_attribute_value("plcHost", plc_host)
49 slice_desc.set_attribute_value("tapPortBase", port_base)
50 # Kills all running processes before starting the experiment
51 slice_desc.set_attribute_value("cleanProc", True)
52 # NOTICE: Setting 'cleanHome' to 'True' will erase all previous
53 # folders in the sliver Home directory, including result files!
54 #slice_desc.set_attribute_value("cleanHome", True)
55 slice_desc.set_attribute_value("plLogLevel", "DEBUG")
58 def create_node(hostname, pl_inet, slice_desc):
59 pl_node = slice_desc.create("Node")
60 pl_node.set_attribute_value("hostname", hostname)
61 pl_node.set_attribute_value("label", hostname)
62 pl_iface = slice_desc.create("NodeInterface")
63 pl_iface.connector("inet").connect(pl_inet.connector("devs"))
64 pl_node.connector("devs").connect(pl_iface.connector("node"))
67 def create_tunnel(node, peer, pl_nodes, slice_desc, subnet):
68 pl_node = pl_nodes[node]
69 pl_peer = pl_nodes[peer]
71 pl_tun = slice_desc.create("TunInterface")
72 pl_tun.set_attribute_value("label", "tun_%s%s" % (node, peer))
73 pl_node.connector("devs").connect(pl_tun.connector("node"))
75 pl_tunpeer = slice_desc.create("TunInterface")
76 pl_tunpeer.set_attribute_value("label", "tun_%s%s" % (peer, node))
77 pl_peer.connector("devs").connect(pl_tunpeer.connector("node"))
79 pl_tun.connector("udp").connect(pl_tunpeer.connector("udp"))
81 iterhosts = subnet.iterhosts()
82 addr = iterhosts.next()
83 ip = pl_tun.add_address()
84 ip.set_attribute_value("Address", addr.exploded)
85 ip.set_attribute_value("NetPrefix", subnet.prefixlen)
87 peeraddr = iterhosts.next()
88 peerip = pl_tunpeer.add_address()
89 peerip.set_attribute_value("Address", peeraddr.exploded)
90 peerip.set_attribute_value("NetPrefix", subnet.prefixlen)
92 def create_ccnd(pl_node, hostname, routes, slice_desc):
93 pl_app = slice_desc.create("CCNxDaemon")
94 # We use a wildcard to replace the TUN IP address of the node during runtime
95 routes = "|".join(map(lambda route: "udp 224.0.23.170 %d 3 1 {#[tun_%s%s].addr[0].[Address]#}" \
96 % (route[1], hostname, route[0]), routes))
97 # Add multicast ccn routes
98 pl_app.set_attribute_value("ccnroutes", routes)
99 pl_app.enable_trace("stdout")
100 pl_app.enable_trace("stderr")
101 pl_app.connector("node").connect(pl_node.connector("apps"))
103 def create_ccnsendchunks(pl_node, slice_desc):
104 pl_app = slice_desc.create("Application")
105 path_to_video = os.path.join(os.path.dirname(os.path.abspath(__file__)),
106 "../big_buck_bunny_240p_mpeg4_lq.ts")
107 pl_app.set_attribute_value("stdin", path_to_video)
108 pl_app.set_attribute_value("command", "ccnsendchunks ccnx:/VIDEO")
109 pl_app.enable_trace("stdout")
110 pl_app.enable_trace("stderr")
111 pl_app.connector("node").connect(pl_node.connector("apps"))
114 def exec_ccncatchunks(slicename, hostname):
115 print "Starting Vlc streamming ..."
116 login = "%s@%s" % (slicename, hostname)
117 command = 'PATH=$PATH:$(ls | egrep nepi-ccnd- | head -1)/bin; ccncatchunks2 ccnx:/VIDEO'
118 proc1 = subprocess.Popen(['ssh', login, command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell = False)
119 proc2 = subprocess.Popen(['vlc',
120 '--sub-filter', 'marq',
122 '(c) copyright 2008, Blender Foundation / www.bigbuckbunny.org',
124 '--no-video-title-show', '-'],
125 stdin=proc1.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
128 def create_ed(hostnames, vsys_vnet, slicename, plc_host, pl_user, pl_pwd, pl_ssh_key,
129 port_base, root_dir):
131 # Create the experiment description object
132 exp_desc = ExperimentDescription()
134 # Create the slice description object
135 slice_desc = create_slice_desc(slicename, plc_host, pl_user, pl_pwd, pl_ssh_key,
136 port_base, root_dir, exp_desc)
138 # Create the Internet box object
139 pl_inet = slice_desc.create("Internet")
141 # Create the Node boxes
146 for hostname in hostnames:
147 pl_node = create_node(hostname, pl_inet, slice_desc)
148 pl_nodes[hostname] = pl_node
150 ccn_routes[hostname] = list()
152 ccn_routes[hostname].append((prev_hostname, port))
153 ccn_routes[prev_hostname].append((hostname, port))
155 prev_hostname = hostname
157 # Get the base network segment (slice vsys_vnet) to assign all the IP addresses
158 # to the virtual interfaces
159 base = ipaddr.IPNetwork(vsys_vnet)
161 # Calculate the number of virtual networks required to connect all the nodes
162 # with all other nodes as the binomial coeficient C(n, 2), with n = #nodes
166 # Validate that we can get 'c' /30 subnetworks
167 if c > math.pow(2, (30 - base.prefixlen)):
168 raise RuntimeError("Insufficient address segment %s for experiment", vsys_vnet)
170 # Create the subnetwors iterator
171 iter_sub = base.iter_subnets(new_prefix=30)
173 # Create tunnels between nodes
174 for i, node in enumerate(hostnames):
175 peers = hostnames[i+1:]
177 subnet = iter_sub.next()
178 create_tunnel(node, peer, pl_nodes, slice_desc, subnet)
180 # Create ccnd daemons in all nodes
181 for hostname, pl_node in pl_nodes.iteritems():
182 routes = ccn_routes[hostname]
183 create_ccnd(pl_node, hostname, routes, slice_desc)
185 # Create a ccnsendchunks application box in the first node
186 hostname = hostnames[0]
187 pl_node = pl_nodes[hostname]
188 pl_app = create_ccnsendchunks(pl_node, slice_desc)
190 return exp_desc, pl_nodes, hostname, pl_app
192 def run(hostnames, vsys_vnet, slicename, plc_host, pl_user, pl_pwd, pl_ssh_key,
193 port_base, root_dir):
195 exp_desc, pl_nodes, hostname, pl_app = create_ed(hostnames, vsys_vnet,
196 slicename, plc_host, pl_user, pl_pwd, pl_ssh_key, port_base,
199 xml = exp_desc.to_xml()
200 controller = ExperimentController(xml, root_dir)
203 while not TERMINATE and controller.status(pl_app.guid) == AS.STATUS_NOT_STARTED:
208 hostname = hostnames[-1]
209 proc = exec_ccncatchunks(slicename, hostname)
211 while not TERMINATE and proc and proc.poll() is None:
216 err = proc.stderr.read()
219 out = proc.stdout.read()
223 controller.shutdown()
225 if __name__ == '__main__':
226 root_dir = tempfile.mkdtemp()
227 slicename = os.environ.get("PL_SLICE")
228 pl_host = os.environ.get("PL_HOST", "www.planet-lab.eu")
229 port_base = 2000 + (os.getpid() % 1000) * 13
230 pl_ssh_key = os.environ.get(
232 "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME'],) )
233 pl_user = os.environ.get('PL_USER')
234 pl_pwd = os.environ.get('PL_PASS')
235 pl_vsys_vnet = os.environ.get('PL_VSYS_NET')
236 pl_hostnames = os.environ.get('PL_HOSTNAMES')
237 default_hostnames = ['openlab02.pl.sophia.inria.fr',
239 'planetlab2.di.unito.it',
240 'merkur.planetlab.haw-hamburg.de',
241 'planetlab1.cs.uit.no',
242 'planetlab3.cs.st-andrews.ac.uk',
243 'planetlab2.cs.uoi.gr',
244 'planetlab3.xeno.cl.cam.ac.uk',
245 'planet2.inf.tu-dresden.de',
246 'planetlab2.csg.uzh.ch',
248 'planetlab-um00.di.uminho.pt',
249 'planetlabpc2.upf.edu',
251 'planetlab2.esprit-tn.com' ]
253 usage = "usage: %prog -s <pl_slice> -H <pl_host> -k <ssh_key> -u <pl_user> -p <pl_password> -v <vsys_vnet> -N <host_names> -c <node_count>"
255 parser = OptionParser(usage=usage)
256 parser.add_option("-s", "--slicename", dest="slicename",
257 help="PlanetLab slicename", default=slicename, type="str")
258 parser.add_option("-H", "--pl-host", dest="pl_host",
259 help="PlanetLab site (e.g. www.planet-lab.eu)",
260 default=pl_host, type="str")
261 parser.add_option("-k", "--ssh-key", dest="pl_ssh_key",
262 help="Path to private ssh key used for PlanetLab authentication",
263 default=pl_ssh_key, type="str")
264 parser.add_option("-u", "--pl-user", dest="pl_user",
265 help="PlanetLab account user (i.e. Registration email address)",
266 default=pl_user, type="str")
267 parser.add_option("-p", "--pl-pwd", dest="pl_pwd",
268 help="PlanetLab account password", default=pl_pwd, type="str")
269 parser.add_option("-v", "--vsys-vnet", dest="vsys_vnet",
270 help="Value of the vsys_vnet tag addigned to your slice. (e.g. 192.168.3.0/16)",
271 default=pl_vsys_vnet, type="str")
272 parser.add_option("-N", "--host-names", dest="hostnames",
273 help="Comma separated list of PlanetLab hostnames to use",
274 default=pl_hostnames, type="str")
275 parser.add_option("-c", "--node-count", dest="node_count",
276 help="Number of nodes to use",
277 default=5, type="str")
278 (options, args) = parser.parse_args()
280 hostnames = map(string.strip, options.hostnames.split(",")) if options.hostnames else default_hostnames
281 if options.node_count > 0 and options.node_count < len(hostnames):
282 hostnames = hostnames[0:options.node_count]
283 vsys_vnet = options.vsys_vnet
284 slicename = options.slicename
285 pl_host = options.pl_host
286 pl_user= options.pl_user
287 pl_pwd = options.pl_pwd
288 pl_ssh_key = options.pl_ssh_key
290 run(hostnames, vsys_vnet, slicename, pl_host, pl_user, pl_pwd, pl_ssh_key,