2 # -*- coding: utf-8 -*-
4 from constants import TESTBED_ID
10 import nepi.util.server as server
20 from nepi.util.constants import ApplicationStatus as AS
22 class Dependency(object):
24 A Dependency is in every respect like an application.
26 It depends on some packages, it may require building binaries, it must deploy
29 But it has no command. Dependencies aren't ever started, or stopped, and have
35 def __init__(self, api=None):
47 self.buildDepends = None
49 self.rpmFusion = False
57 self.add_to_path = True
59 # Those are filled when the app is configured
62 # Those are filled when an actual node is connected
65 # Those are filled when the app is started
66 # Having both pid and ppid makes it harder
67 # for pid rollover to induce tracking mistakes
74 # Spanning tree deployment
76 self._master_passphrase = None
77 self._master_prk = None
78 self._master_puk = None
79 self._master_token = ''.join(map(chr,[rng.randint(0,255)
80 for rng in (random.SystemRandom(),)
81 for i in xrange(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 remote_trace_path(self, whichtrace):
108 if whichtrace in self.TRACES:
109 tracefile = os.path.join(self.home_path, whichtrace)
115 def remote_trace_name(self, whichtrace):
116 if whichtrace in self.TRACES:
120 def sync_trace(self, local_dir, whichtrace):
121 tracefile = self.remote_trace_path(whichtrace)
125 local_path = os.path.join(local_dir, tracefile)
127 # create parent local folders
128 proc = subprocess.Popen(
129 ["mkdir", "-p", os.path.dirname(local_path)],
130 stdout = open("/dev/null","w"),
131 stdin = open("/dev/null","r"))
134 raise RuntimeError, "Failed to synchronize trace"
139 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
143 except RuntimeError, e:
144 raise RuntimeError, "Failed to synchronize trace: %s %s" \
145 % (e.args[0], e.args[1],)
150 # We assume a correct deployment, so recovery only
151 # means we mark this dependency as deployed
155 self._logger.info("Setting up %s", self)
161 def async_setup(self):
162 if not self._setuper:
167 self._setuper._exc.append(sys.exc_info())
168 self._setuper = threading.Thread(
170 self._setuper._exc = []
171 self._setuper.start()
173 def async_setup_wait(self):
175 self._logger.info("Waiting for %s to be setup", self)
179 if self._setuper._exc:
180 exctyp,exval,exctrace = self._setuper._exc[0]
181 raise exctyp,exval,exctrace
183 raise RuntimeError, "Failed to setup application"
185 self._logger.info("Setup ready: %s", self)
189 def _make_home(self):
190 # Make sure all the paths are created where
191 # they have to be created for deployment
194 self._popen_ssh_command(
195 "mkdir -p %(home)s && ( rm -f %(home)s/{pid,build-pid,nepi-build.sh} >/dev/null 2>&1 || /bin/true )" \
196 % { 'home' : server.shell_escape(self.home_path) },
200 except RuntimeError, e:
201 raise RuntimeError, "Failed to set up application %s: %s %s" % (self.home_path, e.args[0], e.args[1],)
204 # Write program input
207 cStringIO.StringIO(self.stdin),
208 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
209 os.path.join(self.home_path, 'stdin') ),
211 except RuntimeError, e:
212 raise RuntimeError, "Failed to set up application %s: %s %s" \
213 % (self.home_path, e.args[0], e.args[1],)
215 def _replace_paths(self, command):
217 Replace all special path tags with shell-escaped actual paths.
219 # need to append ${HOME} if paths aren't absolute, to MAKE them absolute.
220 root = '' if self.home_path.startswith('/') else "${HOME}/"
222 .replace("${SOURCES}", root+server.shell_escape(self.home_path))
223 .replace("${BUILD}", root+server.shell_escape(os.path.join(self.home_path,'build'))) )
225 def _launch_build(self):
226 if self._master is not None:
227 self._do_install_keys()
228 buildscript = self._do_build_slave()
230 buildscript = self._do_build_master()
232 if buildscript is not None:
233 self._logger.info("Building %s", self)
235 # upload build script
239 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
240 os.path.join(self.home_path, 'nepi-build.sh') )
242 except RuntimeError, e:
243 raise RuntimeError, "Failed to set up application %s: %s %s" \
244 % (self.home_path, e.args[0], e.args[1],)
247 self._do_launch_build()
249 def _finish_build(self):
250 self._do_wait_build()
253 def _do_build_slave(self):
254 if not self.sources and not self.build:
257 # Create build script
261 sources = self.sources.split(' ')
263 "%s@%s:%s" % (self._master.node.slicename, self._master.node.hostname,
264 os.path.join(self._master.home_path, os.path.basename(source)),)
265 for source in sources
270 "%s@%s:%s" % (self._master.node.slicename, self._master.node.hostname,
271 os.path.join(self._master.home_path, 'build.tar.gz'),)
274 launch_agent = "{ ( echo -e '#!/bin/sh\\ncat' > .ssh-askpass ) && chmod u+x .ssh-askpass"\
275 " && export SSH_ASKPASS=$(pwd)/.ssh-askpass "\
276 " && ssh-agent > .ssh-agent.sh ; } && . ./.ssh-agent.sh && ( echo $NEPI_MASTER_PASSPHRASE | ssh-add %(prk)s ) && rm -rf %(prk)s %(puk)s" % \
278 'prk' : server.shell_escape(self._master_prk_name),
279 'puk' : server.shell_escape(self._master_puk_name),
282 kill_agent = "kill $SSH_AGENT_PID"
284 waitmaster = "{ . ./.ssh-agent.sh ; while [[ $(ssh -q -o UserKnownHostsFile=%(hostkey)s %(master)s cat %(token_path)s) != %(token)s ]] ; do sleep 5 ; done ; }" % {
285 'hostkey' : 'master_known_hosts',
286 'master' : "%s@%s" % (self._master.node.slicename, self._master.node.hostname),
287 'token_path' : os.path.join(self._master.home_path, 'build.token'),
288 'token' : server.shell_escape(self._master._master_token),
291 syncfiles = "scp -p -o UserKnownHostsFile=%(hostkey)s %(files)s ." % {
292 'hostkey' : 'master_known_hosts',
293 'files' : ' '.join(files),
296 syncfiles += " && tar xzf build.tar.gz"
297 syncfiles += " && ( echo %s > build.token )" % (server.shell_escape(self._master_token),)
298 syncfiles = "{ . ./.ssh-agent.sh ; %s ; }" % (syncfiles,)
300 cleanup = "{ . ./.ssh-agent.sh ; kill $SSH_AGENT_PID ; rm -rf %(prk)s %(puk)s master_known_hosts .ssh-askpass ; }" % {
301 'prk' : server.shell_escape(self._master_prk_name),
302 'puk' : server.shell_escape(self._master_puk_name),
305 slavescript = "( ( %(launch_agent)s && %(waitmaster)s && %(syncfiles)s && %(kill_agent)s && %(cleanup)s ) || %(cleanup)s )" % {
306 'waitmaster' : waitmaster,
307 'syncfiles' : syncfiles,
309 'kill_agent' : kill_agent,
310 'launch_agent' : launch_agent,
311 'home' : server.shell_escape(self.home_path),
314 return cStringIO.StringIO(slavescript)
316 def _do_launch_build(self):
317 script = "bash ./nepi-build.sh"
318 if self._master_passphrase:
319 script = "NEPI_MASTER_PASSPHRASE=%s %s" % (
320 server.shell_escape(self._master_passphrase),
323 (out,err),proc = rspawn.remote_spawn(
325 pidfile = 'build-pid',
326 home = self.home_path,
329 stderr = rspawn.STDOUT,
331 host = self.node.hostname,
333 user = self.node.slicename,
335 ident_key = self.node.ident_path,
336 server_key = self.node.server_key
340 raise RuntimeError, "Failed to set up build slave %s: %s %s" % (self.home_path, out,err,)
346 pidtuple = rspawn.remote_check_pid(
347 os.path.join(self.home_path,'build-pid'),
348 host = self.node.hostname,
350 user = self.node.slicename,
352 ident_key = self.node.ident_path,
353 server_key = self.node.server_key
358 self._build_pid, self._build_ppid = pidtuple
362 delay = min(30,delay*1.2)
364 raise RuntimeError, "Failed to set up build slave %s: cannot get pid" % (self.home_path,)
366 self._logger.info("Deploying %s", self)
368 def _do_wait_build(self):
369 pid = self._build_pid
370 ppid = self._build_ppid
377 status = rspawn.remote_status(
379 host = self.node.hostname,
381 user = self.node.slicename,
383 ident_key = self.node.ident_path,
384 server_key = self.node.server_key
387 if status is rspawn.FINISHED:
388 self._build_pid = self._build_ppid = None
390 elif status is not rspawn.RUNNING:
394 self._build_pid = self._build_ppid = None
398 self._logger.info("Waiting for %s to finish building %s", self,
399 "(build slave)" if self._master is not None else "(build master)")
402 time.sleep(delay*(0.5+random.random()))
403 delay = min(30,delay*1.2)
408 (out, err), proc = self._popen_ssh_command(
409 "cat %(token_path)s" % {
410 'token_path' : os.path.join(self.home_path, 'build.token'),
414 if not proc.wait() and out:
415 slave_token = out.strip()
422 if slave_token != self._master_token:
423 # Get buildlog for the error message
425 (buildlog, err), proc = self._popen_ssh_command(
426 "cat %(buildlog)s" % {
427 'buildlog' : os.path.join(self.home_path, 'buildlog'),
428 'buildscript' : os.path.join(self.home_path, 'nepi-build.sh'),
435 raise RuntimeError, "Failed to set up application %s: "\
436 "build failed, got wrong token from pid %s/%s "\
437 "(expected %r, got %r), see buildlog: %s" % (
438 self.home_path, pid, ppid, self._master_token, slave_token, buildlog)
440 self._logger.info("Built %s", self)
442 def _do_kill_build(self):
443 pid = self._build_pid
444 ppid = self._build_ppid
447 self._logger.info("Killing build of %s", self)
450 host = self.node.hostname,
452 user = self.node.slicename,
454 ident_key = self.node.ident_path
458 def _do_build_master(self):
459 if not self.sources and not self.build and not self.buildDepends:
463 sources = self.sources.split(' ')
469 "%s@%s:%s" % (self.node.slicename, self.node.hostname,
470 os.path.join(self.home_path,'.'),)
472 except RuntimeError, e:
473 raise RuntimeError, "Failed upload source file %r: %s %s" \
474 % (sources, e.args[0], e.args[1],)
476 buildscript = cStringIO.StringIO()
478 if self.buildDepends:
479 # Install build dependencies
481 "sudo -S yum -y install %(packages)s\n" % {
482 'packages' : self.buildDepends
490 "mkdir -p build && ( cd build && ( %(command)s ) )\n" % {
491 'command' : self._replace_paths(self.build),
492 'home' : server.shell_escape(self.home_path),
497 buildscript.write("tar czf build.tar.gz build\n")
500 buildscript.write("echo %(master_token)s > build.token" % {
501 'master_token' : server.shell_escape(self._master_token)
508 def _do_install(self):
510 self._logger.info("Installing %s", self)
512 # Install application
514 self._popen_ssh_command(
515 "cd %(home)s && cd build && ( %(command)s ) > ${HOME}/%(home)s/installlog 2>&1 || ( tail ${HOME}/%(home)s/{install,build}log >&2 && false )" % \
517 'command' : self._replace_paths(self.install),
518 'home' : server.shell_escape(self.home_path),
521 except RuntimeError, e:
522 raise RuntimeError, "Failed install build sources: %s %s" % (e.args[0], e.args[1],)
524 def set_master(self, master):
525 self._master = master
527 def install_keys(self, prk, puk, passphrase):
529 self._master_passphrase = passphrase
530 self._master_prk = prk
531 self._master_puk = puk
532 self._master_prk_name = os.path.basename(prk.name)
533 self._master_puk_name = os.path.basename(puk.name)
535 def _do_install_keys(self):
536 prk = self._master_prk
537 puk = self._master_puk
541 [ prk.name, puk.name ],
542 '%s@%s:%s' % (self.node.slicename, self.node.hostname, self.home_path )
544 except RuntimeError, e:
545 raise RuntimeError, "Failed to set up application deployment keys: %s %s" \
546 % (e.args[0], e.args[1],)
550 cStringIO.StringIO('%s,%s %s\n' % (
551 self._master.node.hostname, socket.gethostbyname(self._master.node.hostname),
552 self._master.node.server_key)),
553 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
554 os.path.join(self.home_path,"master_known_hosts") )
556 except RuntimeError, e:
557 raise RuntimeError, "Failed to set up application deployment keys: %s %s" \
558 % (e.args[0], e.args[1],)
561 self._master_prk = None
562 self._master_puk = None
565 # make sure there's no leftover build processes
566 self._do_kill_build()
569 def _popen_scp(self, src, dst, retry = 3):
572 (out,err),proc = server.popen_scp(
577 ident_key = self.node.ident_path,
578 server_key = self.node.server_key
581 if server.eintr_retry(proc.wait)():
582 raise RuntimeError, (out, err)
583 return (out, err), proc
592 def _popen_ssh_command(self, command, retry = 0, noerrors=False, timeout=None):
593 (out,err),proc = server.popen_ssh_command(
595 host = self.node.hostname,
597 user = self.node.slicename,
599 ident_key = self.node.ident_path,
600 server_key = self.node.server_key,
605 if server.eintr_retry(proc.wait)():
607 raise RuntimeError, (out, err)
608 return (out, err), proc
610 class Application(Dependency):
612 An application also has dependencies, but also a command to be ran and monitored.
614 It adds the output of that command as traces.
617 TRACES = ('stdout','stderr','buildlog', 'output')
619 def __init__(self, api=None):
620 super(Application,self).__init__(api)
631 # Those are filled when the app is started
632 # Having both pid and ppid makes it harder
633 # for pid rollover to induce tracking mistakes
634 self._started = False
638 # Do not add to the python path of nodes
639 self.add_to_path = False
642 return "%s<command:%s%s>" % (
643 self.__class__.__name__,
644 "sudo " if self.sudo else "",
649 self._logger.info("Starting %s", self)
651 # Create shell script with the command
652 # This way, complex commands and scripts can be ran seamlessly
654 command = cStringIO.StringIO()
655 command.write('export PYTHONPATH=$PYTHONPATH:%s\n' % (
656 ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.node.pythonpath])
658 command.write('export PATH=$PATH:%s\n' % (
659 ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.node.pythonpath])
662 for envkey, envvals in self.node.env.iteritems():
663 for envval in envvals:
664 command.write('export %s=%s\n' % (envkey, envval))
665 command.write(self.command)
671 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
672 os.path.join(self.home_path, "app.sh"))
674 except RuntimeError, e:
675 raise RuntimeError, "Failed to set up application: %s %s" \
676 % (e.args[0], e.args[1],)
678 # Start process in a "daemonized" way, using nohup and heavy
679 # stdin/out redirection to avoid connection issues
680 (out,err),proc = rspawn.remote_spawn(
681 self._replace_paths("bash ./app.sh"),
684 home = self.home_path,
685 stdin = 'stdin' if self.stdin is not None else '/dev/null',
686 stdout = 'stdout' if self.stdout else '/dev/null',
687 stderr = 'stderr' if self.stderr else '/dev/null',
690 host = self.node.hostname,
692 user = self.node.slicename,
694 ident_key = self.node.ident_path,
695 server_key = self.node.server_key
699 raise RuntimeError, "Failed to set up application: %s %s" % (out,err,)
704 # Assuming the application is running on PlanetLab,
705 # proper pidfiles should be present at the app's home path.
706 # So we mark this application as started, and check the pidfiles
712 # NOTE: wait a bit for the pidfile to be created
713 if self._started and not self._pid or not self._ppid:
714 pidtuple = rspawn.remote_check_pid(
715 os.path.join(self.home_path,'pid'),
716 host = self.node.hostname,
718 user = self.node.slicename,
720 ident_key = self.node.ident_path,
721 server_key = self.node.server_key
725 self._pid, self._ppid = pidtuple
729 if not self._started:
730 return AS.STATUS_NOT_STARTED
731 elif not self._pid or not self._ppid:
732 return AS.STATUS_NOT_STARTED
734 status = rspawn.remote_status(
735 self._pid, self._ppid,
736 host = self.node.hostname,
738 user = self.node.slicename,
740 ident_key = self.node.ident_path,
741 server_key = self.node.server_key
744 if status is rspawn.NOT_STARTED:
745 return AS.STATUS_NOT_STARTED
746 elif status is rspawn.RUNNING:
747 return AS.STATUS_RUNNING
748 elif status is rspawn.FINISHED:
749 return AS.STATUS_FINISHED
752 return AS.STATUS_NOT_STARTED
755 status = self.status()
756 if status == AS.STATUS_RUNNING:
757 # kill by ppid+pid - SIGTERM first, then try SIGKILL
759 self._pid, self._ppid,
760 host = self.node.hostname,
762 user = self.node.slicename,
764 ident_key = self.node.ident_path,
765 server_key = self.node.server_key,
768 self._logger.info("Killed %s", self)
771 class NepiDependency(Dependency):
773 This dependency adds nepi itself to the python path,
774 so that you may run testbeds within PL nodes.
777 # Class attribute holding a *weak* reference to the shared NEPI tar file
778 # so that they may share it. Don't operate on the file itself, it would
779 # be a mess, just use its path.
780 _shared_nepi_tar = None
782 def __init__(self, api = None):
783 super(NepiDependency, self).__init__(api)
787 self.depends = 'python python-ipaddr python-setuptools'
789 # our sources are in our ad-hoc tarball
790 self.sources = self.tarball.name
792 tarname = os.path.basename(self.tarball.name)
794 # it's already built - just move the tarball into place
795 self.build = "mv -f ${SOURCES}/%s ." % (tarname,)
797 # unpack it into sources, and we're done
798 self.install = "tar xzf ${BUILD}/%s -C .." % (tarname,)
802 if self._tarball is None:
803 shared_tar = self._shared_nepi_tar and self._shared_nepi_tar()
804 if shared_tar is not None:
805 self._tarball = shared_tar
807 # Build an ad-hoc tarball
812 shared_tar = tempfile.NamedTemporaryFile(prefix='nepi-src-', suffix='.tar.gz')
814 proc = subprocess.Popen(
815 ["tar", "czf", shared_tar.name,
816 '-C', os.path.join(os.path.dirname(os.path.dirname(nepi.__file__)),'.'),
818 stdout = open("/dev/null","w"),
819 stdin = open("/dev/null","r"))
822 raise RuntimeError, "Failed to create nepi tarball"
824 self._tarball = self._shared_nepi_tar = shared_tar
828 class NS3Dependency(Dependency):
830 This dependency adds NS3 libraries to the library paths,
831 so that you may run the NS3 testbed within PL nodes.
833 You'll also need the NepiDependency.
836 def __init__(self, api = None):
837 super(NS3Dependency, self).__init__(api)
839 self.buildDepends = 'make waf gcc gcc-c++ gccxml unzip'
841 # We have to download the sources, untar, build...
842 pybindgen_source_url = "http://yans.pl.sophia.inria.fr/trac/nepi/raw-attachment/wiki/WikiStart/pybindgen-r794.tar.gz"
843 pygccxml_source_url = "http://leaseweb.dl.sourceforge.net/project/pygccxml/pygccxml/pygccxml-1.0/pygccxml-1.0.0.zip"
844 ns3_source_url = "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/ns-3.11-nepi/archive/tip.tar.gz"
845 passfd_source_url = "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/python-passfd/archive/tip.tar.gz"
849 " python -c 'import pygccxml, pybindgen, passfd' && "
850 " test -f lib/ns/_core.so && "
851 " test -f lib/ns/__init__.py && "
852 " test -f lib/ns/core.py && "
853 " test -f lib/libns3-core.so && "
854 " LD_LIBRARY_PATH=lib PYTHONPATH=lib python -c 'import ns.core' "
856 # Not working, rebuild
857 # Archive SHA1 sums to check
858 "echo '7158877faff2254e6c094bf18e6b4283cac19137 pygccxml-1.0.0.zip' > archive_sums.txt && "
859 "echo 'a18c2ccffd0df517bc37e2f3a2475092517c43f2 pybindgen-src.tar.gz' >> archive_sums.txt && "
860 " ( " # check existing files
861 " sha1sum -c archive_sums.txt && "
862 " test -f passfd-src.tar.gz && "
863 " test -f ns3-src.tar.gz "
864 " ) || ( " # nope? re-download
865 " rm -f pybindgen-src.zip pygccxml-1.0.0.zip passfd-src.tar.gz ns3-src.tar.gz && "
866 " wget -q -c -O pybindgen-src.tar.gz %(pybindgen_source_url)s && " # continue, to exploit the case when it has already been dl'ed
867 " wget -q -c -O pygccxml-1.0.0.zip %(pygccxml_source_url)s && "
868 " wget -q -c -O passfd-src.tar.gz %(passfd_source_url)s && "
869 " wget -q -c -O ns3-src.tar.gz %(ns3_source_url)s && "
870 " sha1sum -c archive_sums.txt " # Check SHA1 sums when applicable
872 "unzip -n pygccxml-1.0.0.zip && "
873 "mkdir -p pybindgen-src && "
874 "mkdir -p ns3-src && "
875 "mkdir -p passfd-src && "
876 "tar xzf ns3-src.tar.gz --strip-components=1 -C ns3-src && "
877 "tar xzf passfd-src.tar.gz --strip-components=1 -C passfd-src && "
878 "tar xzf pybindgen-src.tar.gz --strip-components=1 -C pybindgen-src && "
879 "rm -rf target && " # mv doesn't like unclean targets
880 "mkdir -p target && "
881 "cd pygccxml-1.0.0 && "
882 "rm -rf unittests docs && " # pygccxml has ~100M of unit tests - excessive - docs aren't needed either
883 "python setup.py build && "
884 "python setup.py install --install-lib ${BUILD}/target && "
885 "python setup.py clean && "
886 "cd ../pybindgen-src && "
887 "export PYTHONPATH=$PYTHONPATH:${BUILD}/target && "
888 "./waf configure --prefix=${BUILD}/target -d release && "
892 "mv -f ${BUILD}/target/lib/python*/site-packages/pybindgen ${BUILD}/target/. && "
893 "rm -rf ${BUILD}/target/lib && "
894 "cd ../passfd-src && "
895 "python setup.py build && "
896 "python setup.py install --install-lib ${BUILD}/target && "
897 "python setup.py clean && "
899 "./waf configure --prefix=${BUILD}/target --with-pybindgen=../pybindgen-src -d release --disable-examples --disable-tests && "
902 "rm -f ${BUILD}/target/lib/*.so && "
903 "cp -a ${BUILD}/ns3-src/build/release/libns3*.so ${BUILD}/target/lib && "
904 "cp -a ${BUILD}/ns3-src/build/release/bindings/python/ns ${BUILD}/target/lib &&"
908 pybindgen_source_url = server.shell_escape(pybindgen_source_url),
909 pygccxml_source_url = server.shell_escape(pygccxml_source_url),
910 ns3_source_url = server.shell_escape(ns3_source_url),
911 passfd_source_url = server.shell_escape(passfd_source_url),
914 # Just move ${BUILD}/target
918 " python -c 'import pygccxml, pybindgen, passfd' && "
919 " test -f lib/ns/_core.so && "
920 " test -f lib/ns/__init__.py && "
921 " test -f lib/ns/core.py && "
922 " test -f lib/libns3-core.so && "
923 " LD_LIBRARY_PATH=lib PYTHONPATH=lib python -c 'import ns.core' "
925 # Not working, reinstall
926 "test -d ${BUILD}/target && "
927 "[[ \"x\" != \"x$(find ${BUILD}/target -mindepth 1 -print -quit)\" ]] &&"
928 "( for i in ${BUILD}/target/* ; do rm -rf ${SOURCES}/${i##*/} ; done ) && " # mv doesn't like unclean targets
929 "mv -f ${BUILD}/target/* ${SOURCES}"
933 # Set extra environment paths
934 self.env['NEPI_NS3BINDINGS'] = "${SOURCES}/lib"
935 self.env['NEPI_NS3LIBRARY'] = "${SOURCES}/lib"
939 if self._tarball is None:
940 shared_tar = self._shared_nepi_tar and self._shared_nepi_tar()
941 if shared_tar is not None:
942 self._tarball = shared_tar
944 # Build an ad-hoc tarball
949 shared_tar = tempfile.NamedTemporaryFile(prefix='nepi-src-', suffix='.tar.gz')
951 proc = subprocess.Popen(
952 ["tar", "czf", shared_tar.name,
953 '-C', os.path.join(os.path.dirname(os.path.dirname(nepi.__file__)),'.'),
955 stdout = open("/dev/null","w"),
956 stdin = open("/dev/null","r"))
959 raise RuntimeError, "Failed to create nepi tarball"
961 self._tarball = self._shared_nepi_tar = shared_tar
965 class YumDependency(Dependency):
967 This dependency is an internal helper class used to
968 efficiently distribute yum-downloaded rpms.
970 It temporarily sets the yum cache as persistent in the
971 build master, and installs all the required packages.
973 The rpm packages left in the yum cache are gathered and
974 distributed by the underlying Dependency in an efficient
975 manner. Build slaves will then install those rpms back in
976 the cache before issuing the install command.
978 When packages have been installed already, nothing but an
979 empty tar is distributed.
982 # Class attribute holding a *weak* reference to the shared NEPI tar file
983 # so that they may share it. Don't operate on the file itself, it would
984 # be a mess, just use its path.
985 _shared_nepi_tar = None
987 def _build_get(self):
988 # canonical representation of dependencies
989 depends = ' '.join( sorted( (self.depends or "").split(' ') ) )
991 # download rpms and pack into a tar archive
993 "sudo -S nice yum -y makecache && "
994 "sudo -S sed -i -r 's/keepcache *= *0/keepcache=1/' /etc/yum.conf && "
996 "sudo -S nice yum -y install %s ; "
997 "rm -f ${BUILD}/packages.tar ; "
998 "tar -C /var/cache/yum -rf ${BUILD}/packages.tar $(cd /var/cache/yum ; find -iname '*.rpm')"
999 " ) || /bin/true ) && "
1000 "sudo -S sed -i -r 's/keepcache *= *1/keepcache=0/' /etc/yum.conf && "
1001 "( sudo -S nice yum -y clean packages || /bin/true ) "
1003 def _build_set(self, value):
1006 build = property(_build_get, _build_set)
1008 def _install_get(self):
1009 # canonical representation of dependencies
1010 depends = ' '.join( sorted( (self.depends or "").split(' ') ) )
1012 # unpack cached rpms into yum cache, install, and cleanup
1014 "sudo -S tar -k --keep-newer-files -C /var/cache/yum -xf packages.tar && "
1015 "sudo -S nice yum -y install %s && "
1016 "( sudo -S nice yum -y clean packages || /bin/true ) "
1018 def _install_set(self, value):
1021 install = property(_install_get, _install_set)