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', '-'], stdin=proc1.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
122 def create_ed(hostnames, vsys_vnet, slicename, plc_host, pl_user, pl_pwd, pl_ssh_key,
123 port_base, root_dir):
125 # Create the experiment description object
126 exp_desc = ExperimentDescription()
128 # Create the slice description object
129 slice_desc = create_slice_desc(slicename, plc_host, pl_user, pl_pwd, pl_ssh_key,
130 port_base, root_dir, exp_desc)
132 # Create the Internet box object
133 pl_inet = slice_desc.create("Internet")
135 # Create the Node boxes
140 for hostname in hostnames:
141 pl_node = create_node(hostname, pl_inet, slice_desc)
142 pl_nodes[hostname] = pl_node
144 ccn_routes[hostname] = list()
146 ccn_routes[hostname].append((prev_hostname, port))
147 ccn_routes[prev_hostname].append((hostname, port))
149 prev_hostname = hostname
151 # Get the base network segment (slice vsys_vnet) to assign all the IP addresses
152 # to the virtual interfaces
153 base = ipaddr.IPNetwork(vsys_vnet)
155 # Calculate the number of virtual networks required to connect all the nodes
156 # with all other nodes as the binomial coeficient C(n, 2), with n = #nodes
160 # Validate that we can get 'c' /30 subnetworks
161 if c > math.pow(2, (30 - base.prefixlen)):
162 raise RuntimeError("Insufficient address segment %s for experiment", vsys_vnet)
164 # Create the subnetwors iterator
165 iter_sub = base.iter_subnets(new_prefix=30)
167 # Create tunnels between nodes
168 for i, node in enumerate(hostnames):
169 peers = hostnames[i+1:]
171 subnet = iter_sub.next()
172 create_tunnel(node, peer, pl_nodes, slice_desc, subnet)
174 # Create ccnd daemons in all nodes
175 for hostname, pl_node in pl_nodes.iteritems():
176 routes = ccn_routes[hostname]
177 create_ccnd(pl_node, hostname, routes, slice_desc)
179 # Create a ccnsendchunks application box in the first node
180 hostname = hostnames[0]
181 pl_node = pl_nodes[hostname]
182 pl_app = create_ccnsendchunks(pl_node, slice_desc)
184 return exp_desc, pl_nodes, hostname, pl_app
186 def run(hostnames, vsys_vnet, slicename, plc_host, pl_user, pl_pwd, pl_ssh_key,
187 port_base, root_dir):
189 exp_desc, pl_nodes, hostname, pl_app = create_ed(hostnames, vsys_vnet,
190 slicename, plc_host, pl_user, pl_pwd, pl_ssh_key, port_base,
193 xml = exp_desc.to_xml()
194 controller = ExperimentController(xml, root_dir)
197 while not TERMINATE and controller.status(pl_app.guid) == AS.STATUS_NOT_STARTED:
202 hostname = hostnames[-1]
203 proc = exec_ccncatchunks(slicename, hostname)
205 while not TERMINATE and proc and proc.poll() is None:
210 err = proc.stderr.read()
213 out = proc.stdout.read()
217 controller.shutdown()
219 if __name__ == '__main__':
220 root_dir = tempfile.mkdtemp()
221 slicename = os.environ.get("PL_SLICE")
222 pl_host = os.environ.get("PL_HOST", "www.planet-lab.eu")
223 port_base = 2000 + (os.getpid() % 1000) * 13
224 pl_ssh_key = os.environ.get(
226 "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME'],) )
227 pl_user = os.environ.get('PL_USER')
228 pl_pwd = os.environ.get('PL_PASS')
229 pl_vsys_vnet = os.environ.get('PL_VSYS_NET')
230 pl_hostnames = os.environ.get('PL_HOSTNAMES')
231 default_hostnames = ['openlab02.pl.sophia.inria.fr',
233 'planetlab2.di.unito.it',
234 'merkur.planetlab.haw-hamburg.de',
235 'planetlab1.cs.uit.no',
236 'planetlab3.cs.st-andrews.ac.uk',
237 'planetlab2.cs.uoi.gr',
238 'planetlab3.xeno.cl.cam.ac.uk',
239 'planet2.inf.tu-dresden.de',
240 'planetlab2.csg.uzh.ch',
242 'planetlab-um00.di.uminho.pt',
243 'planetlabpc2.upf.edu',
245 'planetlab2.esprit-tn.com' ]
247 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>"
249 parser = OptionParser(usage=usage)
250 parser.add_option("-s", "--slicename", dest="slicename",
251 help="PlanetLab slicename", default=slicename, type="str")
252 parser.add_option("-H", "--pl-host", dest="pl_host",
253 help="PlanetLab site (e.g. www.planet-lab.eu)",
254 default=pl_host, type="str")
255 parser.add_option("-k", "--ssh-key", dest="pl_ssh_key",
256 help="Path to private ssh key used for PlanetLab authentication",
257 default=pl_ssh_key, type="str")
258 parser.add_option("-u", "--pl-user", dest="pl_user",
259 help="PlanetLab account user (i.e. Registration email address)",
260 default=pl_user, type="str")
261 parser.add_option("-p", "--pl-pwd", dest="pl_pwd",
262 help="PlanetLab account password", default=pl_pwd, type="str")
263 parser.add_option("-v", "--vsys-vnet", dest="vsys_vnet",
264 help="Value of the vsys_vnet tag addigned to your slice. (e.g. 192.168.3.0/16)",
265 default=pl_vsys_vnet, type="str")
266 parser.add_option("-N", "--host-names", dest="hostnames",
267 help="Comma separated list of PlanetLab hostnames to use",
268 default=pl_hostnames, type="str")
269 parser.add_option("-c", "--node-count", dest="node_count",
270 help="Number of nodes to use",
271 default=5, type="str")
272 (options, args) = parser.parse_args()
274 hostnames = map(string.strip, options.hostnames.split(",")) if options.hostnames else default_hostnames
275 if options.node_count > 0 and options.node_count < len(hostnames):
276 hostnames = hostnames[0:options.node_count]
277 vsys_vnet = options.vsys_vnet
278 slicename = options.slicename
279 pl_host = options.pl_host
280 pl_user= options.pl_user
281 pl_pwd = options.pl_pwd
282 pl_ssh_key = options.pl_ssh_key
284 run(hostnames, vsys_vnet, slicename, pl_host, pl_user, pl_pwd, pl_ssh_key,