1 # -*- coding: utf-8 -*-
3 from constants import TESTBED_ID
9 import nepi.util.server as server
20 from nepi.util.constants import ApplicationStatus as AS
22 _ccnre = re.compile("\s*(udp|tcp)\s+(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\s*$")
24 class Dependency(object):
26 A Dependency is in every respect like an application.
28 It depends on some packages, it may require building binaries, it must deploy
31 But it has no command. Dependencies aren't ever started, or stopped, and have
37 def __init__(self, api=None):
49 self.buildDepends = None
51 self.rpmFusion = False
59 self.add_to_path = True
61 # Those are filled when the app is configured
64 # Those are filled when an actual node is connected
67 # Those are filled when the app is started
68 # Having both pid and ppid makes it harder
69 # for pid rollover to induce tracking mistakes
76 # Spanning tree deployment
78 self._master_passphrase = None
79 self._master_prk = None
80 self._master_puk = None
81 self._master_token = os.urandom(8).encode("hex")
82 self._build_pid = None
83 self._build_ppid = None
86 self._logger = logging.getLogger('nepi.testbeds.planetlab')
91 self.__class__.__name__,
92 ' '.join(filter(bool,(self.depends, self.sources)))
96 if self.home_path is None:
97 raise AssertionError, "Misconfigured application: missing home path"
98 if self.node.ident_path is None or not os.access(self.node.ident_path, os.R_OK):
99 raise AssertionError, "Misconfigured application: missing slice SSH key"
100 if self.node is None:
101 raise AssertionError, "Misconfigured application: unconnected node"
102 if self.node.hostname is None:
103 raise AssertionError, "Misconfigured application: misconfigured node"
104 if self.node.slicename is None:
105 raise AssertionError, "Misconfigured application: unspecified slice"
107 def check_bad_host(self, out, err):
109 Called whenever an operation fails, it's given the output to be checked for
110 telltale signs of unhealthy hosts.
114 def remote_trace_path(self, whichtrace):
115 if whichtrace in self.TRACES:
116 tracefile = os.path.join(self.home_path, whichtrace)
122 def remote_trace_name(self, whichtrace):
123 if whichtrace in self.TRACES:
127 def sync_trace(self, local_dir, whichtrace):
128 tracefile = self.remote_trace_path(whichtrace)
132 local_path = os.path.join(local_dir, tracefile)
134 # create parent local folders
135 proc = subprocess.Popen(
136 ["mkdir", "-p", os.path.dirname(local_path)],
137 stdout = open("/dev/null","w"),
138 stdin = open("/dev/null","r"))
141 raise RuntimeError, "Failed to synchronize trace"
146 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
150 except RuntimeError, e:
151 raise RuntimeError, "Failed to synchronize trace: %s %s" \
152 % (e.args[0], e.args[1],)
157 # We assume a correct deployment, so recovery only
158 # means we mark this dependency as deployed
162 self._logger.info("Setting up %s", self)
168 def async_setup(self):
169 if not self._setuper:
174 self._setuper._exc.append(sys.exc_info())
175 self._setuper = threading.Thread(
177 self._setuper._exc = []
178 self._setuper.start()
180 def async_setup_wait(self):
182 self._logger.info("Waiting for %s to be setup", self)
186 if self._setuper._exc:
187 exctyp,exval,exctrace = self._setuper._exc[0]
188 raise exctyp,exval,exctrace
190 raise RuntimeError, "Failed to setup application"
192 self._logger.info("Setup ready: %s at %s", self, self.node.hostname)
196 def _make_home(self):
197 # Make sure all the paths are created where
198 # they have to be created for deployment
201 self._popen_ssh_command(
202 "mkdir -p %(home)s && ( rm -f %(home)s/{pid,build-pid,nepi-build.sh} >/dev/null 2>&1 || /bin/true )" \
203 % { 'home' : server.shell_escape(self.home_path) },
207 except RuntimeError, e:
208 raise RuntimeError, "Failed to set up application %s: %s %s" % (self.home_path, e.args[0], e.args[1],)
212 if not os.path.isfile(stdin):
213 stdin = cStringIO.StringIO(self.stdin)
215 # Write program input
217 self._popen_scp(stdin,
218 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
219 os.path.join(self.home_path, 'stdin') ),
221 except RuntimeError, e:
222 raise RuntimeError, "Failed to set up application %s: %s %s" \
223 % (self.home_path, e.args[0], e.args[1],)
225 def _replace_paths(self, command):
227 Replace all special path tags with shell-escaped actual paths.
229 # need to append ${HOME} if paths aren't absolute, to MAKE them absolute.
230 root = '' if self.home_path.startswith('/') else "${HOME}/"
232 .replace("${SOURCES}", root+server.shell_escape(self.home_path))
233 .replace("${BUILD}", root+server.shell_escape(os.path.join(self.home_path,'build'))) )
235 def _launch_build(self, trial=0):
236 if self._master is not None:
237 if not trial or self._master_prk is not None:
238 self._do_install_keys()
239 buildscript = self._do_build_slave()
241 buildscript = self._do_build_master()
243 if buildscript is not None:
244 self._logger.info("Building %s at %s", self, self.node.hostname)
246 # upload build script
250 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
251 os.path.join(self.home_path, 'nepi-build.sh') )
253 except RuntimeError, e:
254 raise RuntimeError, "Failed to set up application %s: %s %s" \
255 % (self.home_path, e.args[0], e.args[1],)
258 self._do_launch_build()
260 def _finish_build(self):
261 self._do_wait_build()
264 def _do_build_slave(self):
265 if not self.sources and not self.build:
268 # Create build script
272 sources = self.sources.split(' ')
274 "%s@%s:%s" % (self._master.node.slicename, self._master.node.hostip,
275 os.path.join(self._master.home_path, os.path.basename(source)),)
276 for source in sources
281 "%s@%s:%s" % (self._master.node.slicename, self._master.node.hostip,
282 os.path.join(self._master.home_path, 'build.tar.gz'),)
285 sshopts = "-o ConnectTimeout=30 -o ConnectionAttempts=3 -o ServerAliveInterval=30 -o TCPKeepAlive=yes"
287 launch_agent = "{ ( echo -e '#!/bin/sh\\ncat' > .ssh-askpass ) && chmod u+x .ssh-askpass"\
288 " && export SSH_ASKPASS=$(pwd)/.ssh-askpass "\
289 " && ssh-agent > .ssh-agent.sh ; } && . ./.ssh-agent.sh && ( echo $NEPI_MASTER_PASSPHRASE | ssh-add %(prk)s ) && rm -rf %(prk)s %(puk)s" % \
291 'prk' : server.shell_escape(self._master_prk_name),
292 'puk' : server.shell_escape(self._master_puk_name),
295 kill_agent = "kill $SSH_AGENT_PID"
299 "echo 'Checking master reachability' ; "
300 "if ping -c 3 %(master_host)s && (. ./.ssh-agent.sh > /dev/null ; ssh -o UserKnownHostsFile=%(hostkey)s %(sshopts)s %(master)s echo MASTER SAYS HI ) ; then "
301 "echo 'Master node reachable' ; "
303 "echo 'MASTER NODE UNREACHABLE' && "
306 ". ./.ssh-agent.sh ; "
307 "while [[ $(. ./.ssh-agent.sh > /dev/null ; ssh -q -o UserKnownHostsFile=%(hostkey)s %(sshopts)s %(master)s cat %(token_path)s.retcode || /bin/true) != %(token)s ]] ; do sleep 5 ; done ; "
308 "if [[ $(. ./.ssh-agent.sh > /dev/null ; ssh -q -o UserKnownHostsFile=%(hostkey)s %(sshopts)s %(master)s cat %(token_path)s || /bin/true) != %(token)s ]] ; then echo BAD TOKEN ; exit 1 ; fi ; "
311 'hostkey' : 'master_known_hosts',
312 'master' : "%s@%s" % (self._master.node.slicename, self._master.node.hostip),
313 'master_host' : self._master.node.hostip,
314 'token_path' : os.path.join(self._master.home_path, 'build.token'),
315 'token' : server.shell_escape(self._master._master_token),
319 syncfiles = ". ./.ssh-agent.sh && scp -p -o UserKnownHostsFile=%(hostkey)s %(sshopts)s %(files)s ." % {
320 'hostkey' : 'master_known_hosts',
321 'files' : ' '.join(files),
325 syncfiles += " && tar xzf build.tar.gz"
326 syncfiles += " && ( echo %s > build.token )" % (server.shell_escape(self._master_token),)
327 syncfiles += " && ( echo %s > build.token.retcode )" % (server.shell_escape(self._master_token),)
328 syncfiles = "{ . ./.ssh-agent.sh ; %s ; }" % (syncfiles,)
330 cleanup = "{ . ./.ssh-agent.sh ; kill $SSH_AGENT_PID ; rm -rf %(prk)s %(puk)s master_known_hosts .ssh-askpass ; }" % {
331 'prk' : server.shell_escape(self._master_prk_name),
332 'puk' : server.shell_escape(self._master_puk_name),
335 slavescript = "( ( %(launch_agent)s && %(waitmaster)s && %(syncfiles)s && %(kill_agent)s && %(cleanup)s ) || %(cleanup)s ) ; echo %(token)s > build.token.retcode" % {
336 'waitmaster' : waitmaster,
337 'syncfiles' : syncfiles,
339 'kill_agent' : kill_agent,
340 'launch_agent' : launch_agent,
341 'home' : server.shell_escape(self.home_path),
342 'token' : server.shell_escape(self._master_token),
345 return cStringIO.StringIO(slavescript)
347 def _do_launch_build(self):
348 script = "bash ./nepi-build.sh"
349 if self._master_passphrase:
350 script = "NEPI_MASTER_PASSPHRASE=%s %s" % (
351 server.shell_escape(self._master_passphrase),
354 (out,err),proc = rspawn.remote_spawn(
356 pidfile = 'build-pid',
357 home = self.home_path,
360 stderr = rspawn.STDOUT,
362 host = self.node.hostname,
364 user = self.node.slicename,
366 ident_key = self.node.ident_path,
367 server_key = self.node.server_key,
368 hostip = self.node.hostip,
372 if self.check_bad_host(out, err):
373 self.node.blacklist()
374 raise RuntimeError, "Failed to set up build slave %s: %s %s" % (self.home_path, out,err,)
380 pidtuple = rspawn.remote_check_pid(
381 os.path.join(self.home_path,'build-pid'),
382 host = self.node.hostname,
384 user = self.node.slicename,
386 ident_key = self.node.ident_path,
387 server_key = self.node.server_key,
388 hostip = self.node.hostip
393 self._build_pid, self._build_ppid = pidtuple
397 delay = min(30,delay*1.2)
399 raise RuntimeError, "Failed to set up build slave %s: cannot get pid" % (self.home_path,)
401 self._logger.info("Deploying %s at %s", self, self.node.hostname)
403 def _do_wait_build(self, trial=0):
404 pid = self._build_pid
405 ppid = self._build_ppid
412 status = rspawn.remote_status(
414 host = self.node.hostname,
416 user = self.node.slicename,
418 ident_key = self.node.ident_path,
419 server_key = self.node.server_key,
420 hostip = self.node.hostip
423 if status is rspawn.FINISHED:
424 self._build_pid = self._build_ppid = None
426 elif status is not rspawn.RUNNING:
427 self._logger.warn("Busted waiting for %s to finish building at %s %s", self, self.node.hostname,
428 "(build slave)" if self._master is not None else "(build master)")
430 time.sleep(delay*(5.5+random.random()))
432 self._build_pid = self._build_ppid = None
436 self._logger.info("Waiting for %s to finish building at %s %s", self, self.node.hostname,
437 "(build slave)" if self._master is not None else "(build master)")
440 time.sleep(delay*(0.5+random.random()))
441 delay = min(30,delay*1.2)
447 (out, err), proc = self._popen_ssh_command(
448 "cat %(token_path)s" % {
449 'token_path' : os.path.join(self.home_path, 'build.token'),
453 if not proc.wait() and out:
454 slave_token = out.strip()
461 if slave_token != self._master_token:
462 # Get buildlog for the error message
464 (buildlog, err), proc = self._popen_ssh_command(
465 "cat %(buildlog)s" % {
466 'buildlog' : os.path.join(self.home_path, 'buildlog'),
467 'buildscript' : os.path.join(self.home_path, 'nepi-build.sh'),
474 if self.check_bad_host(buildlog, err):
475 self.node.blacklist()
476 elif self._master and trial < 3 and 'BAD TOKEN' in buildlog or 'BAD TOKEN' in err:
477 # bad sync with master, may try again
478 # but first wait for master
479 self._master.async_setup_wait()
480 self._launch_build(trial+1)
481 return self._do_wait_build(trial+1)
483 return self._do_wait_build(trial+1)
486 self._master_prk = None
487 self._master_puk = None
489 raise RuntimeError, "Failed to set up application %s: "\
490 "build failed, got wrong token from pid %s/%s "\
491 "(expected %r, got %r), see buildlog at %s:\n%s" % (
492 self.home_path, pid, ppid, self._master_token, slave_token, self.node.hostname, buildlog)
495 self._master_prk = None
496 self._master_puk = None
498 self._logger.info("Built %s at %s", self, self.node.hostname)
500 def _do_kill_build(self):
501 pid = self._build_pid
502 ppid = self._build_ppid
505 self._logger.info("Killing build of %s", self)
508 host = self.node.hostname,
510 user = self.node.slicename,
512 ident_key = self.node.ident_path,
513 hostip = self.node.hostip
517 def _do_build_master(self):
518 if not self.sources and not self.build and not self.buildDepends:
522 sources = self.sources.split(' ')
528 "%s@%s:%s" % (self.node.slicename, self.node.hostname,
529 os.path.join(self.home_path,'.'),)
531 except RuntimeError, e:
532 raise RuntimeError, "Failed upload source file %r: %s %s" \
533 % (sources, e.args[0], e.args[1],)
535 buildscript = cStringIO.StringIO()
537 buildscript.write("(\n")
539 if self.buildDepends:
540 # Install build dependencies
542 "sudo -S yum -y install %(packages)s\n" % {
543 'packages' : self.buildDepends
551 "mkdir -p build && ( cd build && ( %(command)s ) )\n" % {
552 'command' : self._replace_paths(self.build),
553 'home' : server.shell_escape(self.home_path),
558 buildscript.write("tar czf build.tar.gz build\n")
561 buildscript.write("echo %(master_token)s > build.token ) ; echo %(master_token)s > build.token.retcode" % {
562 'master_token' : server.shell_escape(self._master_token)
569 def _do_install(self):
571 self._logger.info("Installing %s at %s", self, self.node.hostname)
573 # Install application
575 self._popen_ssh_command(
576 "cd %(home)s && cd build && ( %(command)s ) > ${HOME}/%(home)s/installlog 2>&1 || ( tail ${HOME}/%(home)s/{install,build}log >&2 && false )" % \
578 'command' : self._replace_paths(self.install),
579 'home' : server.shell_escape(self.home_path),
582 except RuntimeError, e:
583 if self.check_bad_host(e.args[0], e.args[1]):
584 self.node.blacklist()
585 raise RuntimeError, "Failed install build sources: %s %s" % (e.args[0], e.args[1],)
587 def set_master(self, master):
588 self._master = master
590 def install_keys(self, prk, puk, passphrase):
592 self._master_passphrase = passphrase
593 self._master_prk = prk
594 self._master_puk = puk
595 self._master_prk_name = os.path.basename(prk.name)
596 self._master_puk_name = os.path.basename(puk.name)
598 def _do_install_keys(self):
599 prk = self._master_prk
600 puk = self._master_puk
604 [ prk.name, puk.name ],
605 '%s@%s:%s' % (self.node.slicename, self.node.hostname, self.home_path )
607 except RuntimeError, e:
608 raise RuntimeError, "Failed to set up application deployment keys: %s %s" \
609 % (e.args[0], e.args[1],)
613 cStringIO.StringIO('%s,%s %s\n' % (
614 self._master.node.hostname, self._master.node.hostip,
615 self._master.node.server_key)),
616 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
617 os.path.join(self.home_path,"master_known_hosts") )
619 except RuntimeError, e:
620 raise RuntimeError, "Failed to set up application deployment keys: %s %s" \
621 % (e.args[0], e.args[1],)
625 # make sure there's no leftover build processes
626 self._do_kill_build()
629 self._master_prk = None
630 self._master_puk = None
633 def _popen_scp(self, src, dst, retry = 3):
636 (out,err),proc = server.popen_scp(
641 ident_key = self.node.ident_path,
642 server_key = self.node.server_key
645 if server.eintr_retry(proc.wait)():
646 raise RuntimeError, (out, err)
647 return (out, err), proc
656 def _popen_ssh_command(self, command, retry = 0, noerrors=False, timeout=None):
657 (out,err),proc = server.popen_ssh_command(
659 host = self.node.hostname,
661 user = self.node.slicename,
663 ident_key = self.node.ident_path,
664 server_key = self.node.server_key,
669 if server.eintr_retry(proc.wait)():
671 raise RuntimeError, (out, err)
672 return (out, err), proc
674 class Application(Dependency):
676 An application also has dependencies, but also a command to be ran and monitored.
678 It adds the output of that command as traces.
681 TRACES = ('stdout','stderr','buildlog', 'output')
683 def __init__(self, api=None):
684 super(Application,self).__init__(api)
695 # Those are filled when the app is started
696 # Having both pid and ppid makes it harder
697 # for pid rollover to induce tracking mistakes
698 self._started = False
702 # Do not add to the python path of nodes
703 self.add_to_path = False
706 return "%s<command:%s%s>" % (
707 self.__class__.__name__,
708 "sudo " if self.sudo else "",
713 self._logger.info("Starting %s", self)
715 # Create shell script with the command
716 # This way, complex commands and scripts can be ran seamlessly
718 command = cStringIO.StringIO()
719 command.write('export PYTHONPATH=$PYTHONPATH:%s\n' % (
720 ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.node.pythonpath])
722 command.write('export PATH=$PATH:%s\n' % (
723 ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.node.pythonpath])
726 for envkey, envvals in self.node.env.iteritems():
727 for envval in envvals:
728 command.write('export %s=%s\n' % (envkey, envval))
729 command.write(self.command)
735 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
736 os.path.join(self.home_path, "app.sh"))
738 except RuntimeError, e:
739 raise RuntimeError, "Failed to set up application: %s %s" \
740 % (e.args[0], e.args[1],)
742 # Start process in a "daemonized" way, using nohup and heavy
743 # stdin/out redirection to avoid connection issues
744 (out,err),proc = rspawn.remote_spawn(
745 self._replace_paths("bash ./app.sh"),
748 home = self.home_path,
749 stdin = 'stdin' if self.stdin is not None else '/dev/null',
750 stdout = 'stdout' if self.stdout else '/dev/null',
751 stderr = 'stderr' if self.stderr else '/dev/null',
754 host = self.node.hostname,
756 user = self.node.slicename,
758 ident_key = self.node.ident_path,
759 server_key = self.node.server_key
763 if self.check_bad_host(out, err):
764 self.node.blacklist()
765 raise RuntimeError, "Failed to set up application: %s %s" % (out,err,)
770 # Assuming the application is running on PlanetLab,
771 # proper pidfiles should be present at the app's home path.
772 # So we mark this application as started, and check the pidfiles
778 # NOTE: wait a bit for the pidfile to be created
779 if self._started and not self._pid or not self._ppid:
780 pidtuple = rspawn.remote_check_pid(
781 os.path.join(self.home_path,'pid'),
782 host = self.node.hostname,
784 user = self.node.slicename,
786 ident_key = self.node.ident_path,
787 server_key = self.node.server_key
791 self._pid, self._ppid = pidtuple
795 if not self._started:
796 return AS.STATUS_NOT_STARTED
797 elif not self._pid or not self._ppid:
798 return AS.STATUS_NOT_STARTED
800 status = rspawn.remote_status(
801 self._pid, self._ppid,
802 host = self.node.hostname,
804 user = self.node.slicename,
806 ident_key = self.node.ident_path,
807 server_key = self.node.server_key
810 if status is rspawn.NOT_STARTED:
811 return AS.STATUS_NOT_STARTED
812 elif status is rspawn.RUNNING:
813 return AS.STATUS_RUNNING
814 elif status is rspawn.FINISHED:
815 return AS.STATUS_FINISHED
818 return AS.STATUS_NOT_STARTED
821 status = self.status()
822 if status == AS.STATUS_RUNNING:
823 # kill by ppid+pid - SIGTERM first, then try SIGKILL
825 self._pid, self._ppid,
826 host = self.node.hostname,
828 user = self.node.slicename,
830 ident_key = self.node.ident_path,
831 server_key = self.node.server_key,
834 self._logger.info("Killed %s", self)
837 class NepiDependency(Dependency):
839 This dependency adds nepi itself to the python path,
840 so that you may run testbeds within PL nodes.
843 # Class attribute holding a *weak* reference to the shared NEPI tar file
844 # so that they may share it. Don't operate on the file itself, it would
845 # be a mess, just use its path.
846 _shared_nepi_tar = None
848 def __init__(self, api = None):
849 super(NepiDependency, self).__init__(api)
853 self.depends = 'python python-ipaddr python-setuptools'
855 # our sources are in our ad-hoc tarball
856 self.sources = self.tarball.name
858 tarname = os.path.basename(self.tarball.name)
860 # it's already built - just move the tarball into place
861 self.build = "mv -f ${SOURCES}/%s ." % (tarname,)
863 # unpack it into sources, and we're done
864 self.install = "tar xzf ${BUILD}/%s -C .." % (tarname,)
868 if self._tarball is None:
869 shared_tar = self._shared_nepi_tar and self._shared_nepi_tar()
870 if shared_tar is not None:
871 self._tarball = shared_tar
873 # Build an ad-hoc tarball
878 shared_tar = tempfile.NamedTemporaryFile(prefix='nepi-src-', suffix='.tar.gz')
880 proc = subprocess.Popen(
881 ["tar", "czf", shared_tar.name,
882 '-C', os.path.join(os.path.dirname(os.path.dirname(nepi.__file__)),'.'),
884 stdout = open("/dev/null","w"),
885 stdin = open("/dev/null","r"))
888 raise RuntimeError, "Failed to create nepi tarball"
890 self._tarball = self._shared_nepi_tar = shared_tar
894 class NS3Dependency(Dependency):
896 This dependency adds NS3 libraries to the library paths,
897 so that you may run the NS3 testbed within PL nodes.
899 You'll also need the NepiDependency.
902 def __init__(self, api = None):
903 super(NS3Dependency, self).__init__(api)
905 self.buildDepends = 'make waf gcc gcc-c++ gccxml unzip bzr'
907 # We have to download the sources, untar, build...
908 pygccxml_source_url = "http://leaseweb.dl.sourceforge.net/project/pygccxml/pygccxml/pygccxml-1.0/pygccxml-1.0.0.zip"
909 ns3_source_url = "http://nepi.pl.sophia.inria.fr/code/nepi-ns3.13/archive/tip.tar.gz"
910 passfd_source_url = "http://nepi.pl.sophia.inria.fr/code/python-passfd/archive/tip.tar.gz"
912 pybindgen_version = "797"
917 " python -c 'import pygccxml, pybindgen, passfd' && "
918 " test -f lib/ns/_core.so && "
919 " test -f lib/ns/__init__.py && "
920 " test -f lib/ns/core.py && "
921 " test -f lib/libns3-core.so && "
922 " LD_LIBRARY_PATH=lib PYTHONPATH=lib python -c 'import ns.core' "
924 # Not working, rebuild
925 # Archive SHA1 sums to check
926 "echo '7158877faff2254e6c094bf18e6b4283cac19137 pygccxml-1.0.0.zip' > archive_sums.txt && "
927 " ( " # check existing files
928 " sha1sum -c archive_sums.txt && "
929 " test -f passfd-src.tar.gz && "
930 " test -f ns3-src.tar.gz "
931 " ) || ( " # nope? re-download
932 " rm -rf pybindgen pygccxml-1.0.0.zip passfd-src.tar.gz ns3-src.tar.gz && "
933 " bzr checkout lp:pybindgen -r %(pybindgen_version)s && " # continue, to exploit the case when it has already been dl'ed
934 " wget -q -c -O pygccxml-1.0.0.zip %(pygccxml_source_url)s && "
935 " wget -q -c -O passfd-src.tar.gz %(passfd_source_url)s && "
936 " wget -q -c -O ns3-src.tar.gz %(ns3_source_url)s && "
937 " sha1sum -c archive_sums.txt " # Check SHA1 sums when applicable
939 "unzip -n pygccxml-1.0.0.zip && "
940 "mkdir -p ns3-src && "
941 "mkdir -p passfd-src && "
942 "tar xzf ns3-src.tar.gz --strip-components=1 -C ns3-src && "
943 "tar xzf passfd-src.tar.gz --strip-components=1 -C passfd-src && "
944 "rm -rf target && " # mv doesn't like unclean targets
945 "mkdir -p target && "
946 "cd pygccxml-1.0.0 && "
947 "rm -rf unittests docs && " # pygccxml has ~100M of unit tests - excessive - docs aren't needed either
948 "python setup.py build && "
949 "python setup.py install --install-lib ${BUILD}/target && "
950 "python setup.py clean && "
951 "cd ../pybindgen && "
952 "export PYTHONPATH=$PYTHONPATH:${BUILD}/target && "
953 "./waf configure --prefix=${BUILD}/target -d release && "
957 "mv -f ${BUILD}/target/lib/python*/site-packages/pybindgen ${BUILD}/target/. && "
958 "rm -rf ${BUILD}/target/lib && "
959 "cd ../passfd-src && "
960 "python setup.py build && "
961 "python setup.py install --install-lib ${BUILD}/target && "
962 "python setup.py clean && "
964 "./waf configure --prefix=${BUILD}/target --with-pybindgen=../pybindgen-src -d release --disable-examples --disable-tests && "
967 "rm -f ${BUILD}/target/lib/*.so && "
968 "cp -a ${BUILD}/ns3-src/build/libns3*.so ${BUILD}/target/lib && "
969 "cp -a ${BUILD}/ns3-src/build/bindings/python/ns ${BUILD}/target/lib &&"
973 pybindgen_version = server.shell_escape(pybindgen_version),
974 pygccxml_source_url = server.shell_escape(pygccxml_source_url),
975 ns3_source_url = server.shell_escape(ns3_source_url),
976 passfd_source_url = server.shell_escape(passfd_source_url),
979 # Just move ${BUILD}/target
983 " python -c 'import pygccxml, pybindgen, passfd' && "
984 " test -f lib/ns/_core.so && "
985 " test -f lib/ns/__init__.py && "
986 " test -f lib/ns/core.py && "
987 " test -f lib/libns3-core.so && "
988 " LD_LIBRARY_PATH=lib PYTHONPATH=lib python -c 'import ns.core' "
990 # Not working, reinstall
991 "test -d ${BUILD}/target && "
992 "[[ \"x\" != \"x$(find ${BUILD}/target -mindepth 1 -print -quit)\" ]] &&"
993 "( for i in ${BUILD}/target/* ; do rm -rf ${SOURCES}/${i##*/} ; done ) && " # mv doesn't like unclean targets
994 "mv -f ${BUILD}/target/* ${SOURCES}"
998 # Set extra environment paths
999 self.env['NEPI_NS3BINDINGS'] = "${SOURCES}/lib"
1000 self.env['NEPI_NS3LIBRARY'] = "${SOURCES}/lib"
1004 if self._tarball is None:
1005 shared_tar = self._shared_nepi_tar and self._shared_nepi_tar()
1006 if shared_tar is not None:
1007 self._tarball = shared_tar
1009 # Build an ad-hoc tarball
1014 shared_tar = tempfile.NamedTemporaryFile(prefix='nepi-src-', suffix='.tar.gz')
1016 proc = subprocess.Popen(
1017 ["tar", "czf", shared_tar.name,
1018 '-C', os.path.join(os.path.dirname(os.path.dirname(nepi.__file__)),'.'),
1020 stdout = open("/dev/null","w"),
1021 stdin = open("/dev/null","r"))
1024 raise RuntimeError, "Failed to create nepi tarball"
1026 self._tarball = self._shared_nepi_tar = shared_tar
1028 return self._tarball
1030 class YumDependency(Dependency):
1032 This dependency is an internal helper class used to
1033 efficiently distribute yum-downloaded rpms.
1035 It temporarily sets the yum cache as persistent in the
1036 build master, and installs all the required packages.
1038 The rpm packages left in the yum cache are gathered and
1039 distributed by the underlying Dependency in an efficient
1040 manner. Build slaves will then install those rpms back in
1041 the cache before issuing the install command.
1043 When packages have been installed already, nothing but an
1044 empty tar is distributed.
1047 # Class attribute holding a *weak* reference to the shared NEPI tar file
1048 # so that they may share it. Don't operate on the file itself, it would
1049 # be a mess, just use its path.
1050 _shared_nepi_tar = None
1052 def _build_get(self):
1053 # canonical representation of dependencies
1054 depends = ' '.join( sorted( (self.depends or "").split(' ') ) )
1056 # download rpms and pack into a tar archive
1058 "sudo -S nice yum -y makecache && "
1059 "sudo -S sed -i -r 's/keepcache *= *0/keepcache=1/' /etc/yum.conf && "
1061 "sudo -S nice yum -y install %s ; "
1062 "rm -f ${BUILD}/packages.tar ; "
1063 "tar -C /var/cache/yum -rf ${BUILD}/packages.tar $(cd /var/cache/yum ; find -iname '*.rpm')"
1064 " ) || /bin/true ) && "
1065 "sudo -S sed -i -r 's/keepcache *= *1/keepcache=0/' /etc/yum.conf && "
1066 "( sudo -S nice yum -y clean packages || /bin/true ) "
1068 def _build_set(self, value):
1071 build = property(_build_get, _build_set)
1073 def _install_get(self):
1074 # canonical representation of dependencies
1075 depends = ' '.join( sorted( (self.depends or "").split(' ') ) )
1077 # unpack cached rpms into yum cache, install, and cleanup
1079 "sudo -S tar -k --keep-newer-files -C /var/cache/yum -xf packages.tar && "
1080 "sudo -S nice yum -y install %s && "
1081 "( sudo -S nice yum -y clean packages || /bin/true ) "
1083 def _install_set(self, value):
1086 install = property(_install_get, _install_set)
1088 def check_bad_host(self, out, err):
1089 badre = re.compile(r'(?:'
1090 r'The GPG keys listed for the ".*" repository are already installed but they are not correct for this package'
1091 r'|Error: Cannot retrieve repository metadata (repomd.xml) for repository: .*[.] Please verify its path and try again'
1092 r'|Error: disk I/O error'
1093 r'|MASTER NODE UNREACHABLE'
1096 return badre.search(out) or badre.search(err) or self.node.check_bad_host(out,err)
1099 class CCNxDaemon(Application):
1101 An application also has dependencies, but also a command to be ran and monitored.
1103 It adds the output of that command as traces.
1106 def __init__(self, api=None):
1107 super(CCNxDaemon,self).__init__(api)
1110 self.ccnLocalPort = None
1111 self.ccnRoutes = None
1112 self.ccnxVersion = "ccnx-0.6.0"
1114 self.ccnx_0_5_1_sources = "http://www.ccnx.org/releases/ccnx-0.5.1.tar.gz"
1115 self.ccnx_0_6_0_sources = "http://www.ccnx.org/releases/ccnx-0.6.0.tar.gz"
1116 self.buildDepends = 'make gcc development-tools openssl-devel expat-devel libpcap-devel libxml2-devel'
1118 self.ccnx_0_5_1_build = (
1121 " test -d ccnx-0.5.1-src/build/bin "
1123 # Not working, rebuild
1125 " mkdir -p ccnx-0.5.1-src && "
1126 " wget -q -c -O ccnx-0.5.1-src.tar.gz %(ccnx_source_url)s &&"
1127 " tar xf ccnx-0.5.1-src.tar.gz --strip-components=1 -C ccnx-0.5.1-src "
1129 "cd ccnx-0.5.1-src && "
1130 "mkdir -p build/include &&"
1131 "mkdir -p build/lib &&"
1132 "mkdir -p build/bin &&"
1134 "INSTALL_BASE=$I ./configure &&"
1135 "make && make install"
1137 ccnx_source_url = server.shell_escape(self.ccnx_0_5_1_sources),
1140 self.ccnx_0_5_1_install = (
1142 " test -d ${BUILD}/ccnx-0.5.1-src/build/bin && "
1143 " cp -r ${BUILD}/ccnx-0.5.1-src/build/bin ${SOURCES}"
1147 self.ccnx_0_6_0_build = (
1150 " test -d ccnx-0.6.0-src/build/bin "
1152 # Not working, rebuild
1154 " mkdir -p ccnx-0.6.0-src && "
1155 " wget -q -c -O ccnx-0.6.0-src.tar.gz %(ccnx_source_url)s &&"
1156 " tar xf ccnx-0.6.0-src.tar.gz --strip-components=1 -C ccnx-0.6.0-src "
1158 "cd ccnx-0.6.0-src && "
1159 "./configure && make"
1161 ccnx_source_url = server.shell_escape(self.ccnx_0_6_0_sources),
1164 self.ccnx_0_6_0_install = (
1166 " test -d ${BUILD}/ccnx-0.6.0-src/bin && "
1167 " cp -r ${BUILD}/ccnx-0.6.0-src/bin ${SOURCES}"
1171 self.env['PATH'] = "$PATH:${SOURCES}/bin"
1174 # setting ccn sources
1176 if self.ccnxVersion == 'ccnx-0.6.0':
1177 self.build = self.ccnx_0_6_0_build
1178 elif self.ccnxVersion == 'ccnx-0.5.1':
1179 self.build = self.ccnx_0_5_1_build
1181 if not self.install:
1182 if self.ccnxVersion == 'ccnx-0.6.0':
1183 self.install = self.ccnx_0_6_0_install
1184 elif self.ccnxVersion == 'ccnx-0.5.1':
1185 self.install = self.ccnx_0_5_1_install
1187 super(CCNxDaemon, self).setup()
1191 if self.ccnLocalPort:
1192 self.command = "export CCN_LOCAL_PORT=%s ; " % self.ccnLocalPort
1193 self.command += " ccndstart "
1195 # configure ccn routes
1197 routes = self.ccnRoutes.split("|")
1199 if self.ccnLocalPort:
1200 routes = map(lambda route: "%s %s" %(route,
1201 self.ccnLocalPort) if _ccnre.match(route) else route,
1204 routes = map(lambda route: "ccndc add ccnx:/ %s" % route,
1207 routescmd = " ; ".join(routes)
1208 self.command += " ; "
1209 self.command += routescmd
1211 # Start will be invoked in prestart step
1212 super(CCNxDaemon, self).start()
1215 self._logger.info("Killing %s", self)
1217 command = "${SOURCES}/bin/ccndstop"
1219 if self.ccnLocalPort:
1220 self.command = "export CCN_LOCAL_PORT=%s; %s" % (self.ccnLocalPort, command)
1222 cmd = self._replace_paths(command)
1223 command = cStringIO.StringIO()
1230 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
1231 os.path.join(self.home_path, "kill.sh"))
1233 except RuntimeError, e:
1234 raise RuntimeError, "Failed to kill ccndxdaemon: %s %s" \
1235 % (e.args[0], e.args[1],)
1238 script = "bash ./kill.sh"
1239 (out,err),proc = rspawn.remote_spawn(
1241 pidfile = 'kill-pid',
1242 home = self.home_path,
1243 stdin = '/dev/null',
1245 stderr = rspawn.STDOUT,
1247 host = self.node.hostname,
1249 user = self.node.slicename,
1251 ident_key = self.node.ident_path,
1252 server_key = self.node.server_key,
1253 hostip = self.node.hostip,
1257 raise RuntimeError, "Failed to kill cnnxdaemon: %s %s" % (out,err,)
1259 super(CCNxDaemon, self).kill()