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
376 status = rspawn.remote_status(
378 host = self.node.hostname,
380 user = self.node.slicename,
382 ident_key = self.node.ident_path,
383 server_key = self.node.server_key
386 if status is not rspawn.RUNNING:
387 self._build_pid = self._build_ppid = None
391 self._logger.info("Waiting for %s to finish building %s", self,
392 "(build slave)" if self._master is not None else "(build master)")
395 time.sleep(delay*(0.5+random.random()))
396 delay = min(30,delay*1.2)
401 (out, err), proc = self._popen_ssh_command(
402 "cat %(token_path)s" % {
403 'token_path' : os.path.join(self.home_path, 'build.token'),
407 if not proc.wait() and out:
408 slave_token = out.strip()
415 if slave_token != self._master_token:
416 # Get buildlog for the error message
418 (buildlog, err), proc = self._popen_ssh_command(
419 "cat %(buildlog)s" % {
420 'buildlog' : os.path.join(self.home_path, 'buildlog'),
421 'buildscript' : os.path.join(self.home_path, 'nepi-build.sh'),
428 raise RuntimeError, "Failed to set up application %s: "\
429 "build failed, got wrong token from pid %s/%s "\
430 "(expected %r, got %r), see buildlog: %s" % (
431 self.home_path, pid, ppid, self._master_token, slave_token, buildlog)
433 self._logger.info("Built %s", self)
435 def _do_kill_build(self):
436 pid = self._build_pid
437 ppid = self._build_ppid
440 self._logger.info("Killing build of %s", self)
443 host = self.node.hostname,
445 user = self.node.slicename,
447 ident_key = self.node.ident_path
451 def _do_build_master(self):
452 if not self.sources and not self.build and not self.buildDepends:
456 sources = self.sources.split(' ')
462 "%s@%s:%s" % (self.node.slicename, self.node.hostname,
463 os.path.join(self.home_path,'.'),)
465 except RuntimeError, e:
466 raise RuntimeError, "Failed upload source file %r: %s %s" \
467 % (sources, e.args[0], e.args[1],)
469 buildscript = cStringIO.StringIO()
471 if self.buildDepends:
472 # Install build dependencies
474 "sudo -S yum -y install %(packages)s\n" % {
475 'packages' : self.buildDepends
483 "mkdir -p build && ( cd build && ( %(command)s ) )\n" % {
484 'command' : self._replace_paths(self.build),
485 'home' : server.shell_escape(self.home_path),
490 buildscript.write("tar czf build.tar.gz build\n")
493 buildscript.write("echo %(master_token)s > build.token" % {
494 'master_token' : server.shell_escape(self._master_token)
501 def _do_install(self):
503 self._logger.info("Installing %s", self)
505 # Install application
507 self._popen_ssh_command(
508 "cd %(home)s && cd build && ( %(command)s ) > ${HOME}/%(home)s/installlog 2>&1 || ( tail ${HOME}/%(home)s/{install,build}log >&2 && false )" % \
510 'command' : self._replace_paths(self.install),
511 'home' : server.shell_escape(self.home_path),
514 except RuntimeError, e:
515 raise RuntimeError, "Failed install build sources: %s %s" % (e.args[0], e.args[1],)
517 def set_master(self, master):
518 self._master = master
520 def install_keys(self, prk, puk, passphrase):
522 self._master_passphrase = passphrase
523 self._master_prk = prk
524 self._master_puk = puk
525 self._master_prk_name = os.path.basename(prk.name)
526 self._master_puk_name = os.path.basename(puk.name)
528 def _do_install_keys(self):
529 prk = self._master_prk
530 puk = self._master_puk
534 [ prk.name, puk.name ],
535 '%s@%s:%s' % (self.node.slicename, self.node.hostname, self.home_path )
537 except RuntimeError, e:
538 raise RuntimeError, "Failed to set up application deployment keys: %s %s" \
539 % (e.args[0], e.args[1],)
543 cStringIO.StringIO('%s,%s %s\n' % (
544 self._master.node.hostname, socket.gethostbyname(self._master.node.hostname),
545 self._master.node.server_key)),
546 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
547 os.path.join(self.home_path,"master_known_hosts") )
549 except RuntimeError, e:
550 raise RuntimeError, "Failed to set up application deployment keys: %s %s" \
551 % (e.args[0], e.args[1],)
554 self._master_prk = None
555 self._master_puk = None
558 # make sure there's no leftover build processes
559 self._do_kill_build()
562 def _popen_scp(self, src, dst, retry = 3):
565 (out,err),proc = server.popen_scp(
570 ident_key = self.node.ident_path,
571 server_key = self.node.server_key
574 if server.eintr_retry(proc.wait)():
575 raise RuntimeError, (out, err)
576 return (out, err), proc
585 def _popen_ssh_command(self, command, retry = 0, noerrors=False, timeout=None):
586 (out,err),proc = server.popen_ssh_command(
588 host = self.node.hostname,
590 user = self.node.slicename,
592 ident_key = self.node.ident_path,
593 server_key = self.node.server_key,
598 if server.eintr_retry(proc.wait)():
600 raise RuntimeError, (out, err)
601 return (out, err), proc
603 class Application(Dependency):
605 An application also has dependencies, but also a command to be ran and monitored.
607 It adds the output of that command as traces.
610 TRACES = ('stdout','stderr','buildlog', 'output')
612 def __init__(self, api=None):
613 super(Application,self).__init__(api)
624 # Those are filled when the app is started
625 # Having both pid and ppid makes it harder
626 # for pid rollover to induce tracking mistakes
627 self._started = False
631 # Do not add to the python path of nodes
632 self.add_to_path = False
635 return "%s<command:%s%s>" % (
636 self.__class__.__name__,
637 "sudo " if self.sudo else "",
642 self._logger.info("Starting %s", self)
644 # Create shell script with the command
645 # This way, complex commands and scripts can be ran seamlessly
647 command = cStringIO.StringIO()
648 command.write('export PYTHONPATH=$PYTHONPATH:%s\n' % (
649 ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.node.pythonpath])
651 command.write('export PATH=$PATH:%s\n' % (
652 ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.node.pythonpath])
655 for envkey, envvals in self.node.env.iteritems():
656 for envval in envvals:
657 command.write('export %s=%s\n' % (envkey, envval))
658 command.write(self.command)
664 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
665 os.path.join(self.home_path, "app.sh"))
667 except RuntimeError, e:
668 raise RuntimeError, "Failed to set up application: %s %s" \
669 % (e.args[0], e.args[1],)
671 # Start process in a "daemonized" way, using nohup and heavy
672 # stdin/out redirection to avoid connection issues
673 (out,err),proc = rspawn.remote_spawn(
674 self._replace_paths("bash ./app.sh"),
677 home = self.home_path,
678 stdin = 'stdin' if self.stdin is not None else '/dev/null',
679 stdout = 'stdout' if self.stdout else '/dev/null',
680 stderr = 'stderr' if self.stderr else '/dev/null',
683 host = self.node.hostname,
685 user = self.node.slicename,
687 ident_key = self.node.ident_path,
688 server_key = self.node.server_key
692 raise RuntimeError, "Failed to set up application: %s %s" % (out,err,)
697 # Assuming the application is running on PlanetLab,
698 # proper pidfiles should be present at the app's home path.
699 # So we mark this application as started, and check the pidfiles
705 # NOTE: wait a bit for the pidfile to be created
706 if self._started and not self._pid or not self._ppid:
707 pidtuple = rspawn.remote_check_pid(
708 os.path.join(self.home_path,'pid'),
709 host = self.node.hostname,
711 user = self.node.slicename,
713 ident_key = self.node.ident_path,
714 server_key = self.node.server_key
718 self._pid, self._ppid = pidtuple
722 if not self._started:
723 return AS.STATUS_NOT_STARTED
724 elif not self._pid or not self._ppid:
725 return AS.STATUS_NOT_STARTED
727 status = rspawn.remote_status(
728 self._pid, self._ppid,
729 host = self.node.hostname,
731 user = self.node.slicename,
733 ident_key = self.node.ident_path,
734 server_key = self.node.server_key
737 if status is rspawn.NOT_STARTED:
738 return AS.STATUS_NOT_STARTED
739 elif status is rspawn.RUNNING:
740 return AS.STATUS_RUNNING
741 elif status is rspawn.FINISHED:
742 return AS.STATUS_FINISHED
745 return AS.STATUS_NOT_STARTED
748 status = self.status()
749 if status == AS.STATUS_RUNNING:
750 # kill by ppid+pid - SIGTERM first, then try SIGKILL
752 self._pid, self._ppid,
753 host = self.node.hostname,
755 user = self.node.slicename,
757 ident_key = self.node.ident_path,
758 server_key = self.node.server_key,
761 self._logger.info("Killed %s", self)
764 class NepiDependency(Dependency):
766 This dependency adds nepi itself to the python path,
767 so that you may run testbeds within PL nodes.
770 # Class attribute holding a *weak* reference to the shared NEPI tar file
771 # so that they may share it. Don't operate on the file itself, it would
772 # be a mess, just use its path.
773 _shared_nepi_tar = None
775 def __init__(self, api = None):
776 super(NepiDependency, self).__init__(api)
780 self.depends = 'python python-ipaddr python-setuptools'
782 # our sources are in our ad-hoc tarball
783 self.sources = self.tarball.name
785 tarname = os.path.basename(self.tarball.name)
787 # it's already built - just move the tarball into place
788 self.build = "mv -f ${SOURCES}/%s ." % (tarname,)
790 # unpack it into sources, and we're done
791 self.install = "tar xzf ${BUILD}/%s -C .." % (tarname,)
795 if self._tarball is None:
796 shared_tar = self._shared_nepi_tar and self._shared_nepi_tar()
797 if shared_tar is not None:
798 self._tarball = shared_tar
800 # Build an ad-hoc tarball
805 shared_tar = tempfile.NamedTemporaryFile(prefix='nepi-src-', suffix='.tar.gz')
807 proc = subprocess.Popen(
808 ["tar", "czf", shared_tar.name,
809 '-C', os.path.join(os.path.dirname(os.path.dirname(nepi.__file__)),'.'),
811 stdout = open("/dev/null","w"),
812 stdin = open("/dev/null","r"))
815 raise RuntimeError, "Failed to create nepi tarball"
817 self._tarball = self._shared_nepi_tar = shared_tar
821 class NS3Dependency(Dependency):
823 This dependency adds NS3 libraries to the library paths,
824 so that you may run the NS3 testbed within PL nodes.
826 You'll also need the NepiDependency.
829 def __init__(self, api = None):
830 super(NS3Dependency, self).__init__(api)
832 self.buildDepends = 'make waf gcc gcc-c++ gccxml unzip'
834 # We have to download the sources, untar, build...
835 pybindgen_source_url = "http://yans.pl.sophia.inria.fr/trac/nepi/raw-attachment/wiki/WikiStart/pybindgen-r794.tar.gz"
836 pygccxml_source_url = "http://leaseweb.dl.sourceforge.net/project/pygccxml/pygccxml/pygccxml-1.0/pygccxml-1.0.0.zip"
837 ns3_source_url = "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/ns-3.11-nepi/archive/tip.tar.gz"
838 passfd_source_url = "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/python-passfd/archive/tip.tar.gz"
842 " python -c 'import pygccxml, pybindgen, passfd' && "
843 " test -f lib/ns/_core.so && "
844 " test -f lib/ns/__init__.py && "
845 " test -f lib/ns/core.py && "
846 " test -f lib/libns3-core.so && "
847 " LD_LIBRARY_PATH=lib PYTHONPATH=lib python -c 'import ns.core' "
849 # Not working, rebuild
850 # Archive SHA1 sums to check
851 "echo '7158877faff2254e6c094bf18e6b4283cac19137 pygccxml-1.0.0.zip' > archive_sums.txt && "
852 "echo 'a18c2ccffd0df517bc37e2f3a2475092517c43f2 pybindgen-src.tar.gz' >> archive_sums.txt && "
853 " ( " # check existing files
854 " sha1sum -c archive_sums.txt && "
855 " test -f passfd-src.tar.gz && "
856 " test -f ns3-src.tar.gz "
857 " ) || ( " # nope? re-download
858 " rm -f pybindgen-src.zip pygccxml-1.0.0.zip passfd-src.tar.gz ns3-src.tar.gz && "
859 " wget -q -c -O pybindgen-src.tar.gz %(pybindgen_source_url)s && " # continue, to exploit the case when it has already been dl'ed
860 " wget -q -c -O pygccxml-1.0.0.zip %(pygccxml_source_url)s && "
861 " wget -q -c -O passfd-src.tar.gz %(passfd_source_url)s && "
862 " wget -q -c -O ns3-src.tar.gz %(ns3_source_url)s && "
863 " sha1sum -c archive_sums.txt " # Check SHA1 sums when applicable
865 "unzip -n pygccxml-1.0.0.zip && "
866 "mkdir -p pybindgen-src && "
867 "mkdir -p ns3-src && "
868 "mkdir -p passfd-src && "
869 "tar xzf ns3-src.tar.gz --strip-components=1 -C ns3-src && "
870 "tar xzf passfd-src.tar.gz --strip-components=1 -C passfd-src && "
871 "tar xzf pybindgen-src.tar.gz --strip-components=1 -C pybindgen-src && "
872 "rm -rf target && " # mv doesn't like unclean targets
873 "mkdir -p target && "
874 "cd pygccxml-1.0.0 && "
875 "rm -rf unittests docs && " # pygccxml has ~100M of unit tests - excessive - docs aren't needed either
876 "python setup.py build && "
877 "python setup.py install --install-lib ${BUILD}/target && "
878 "python setup.py clean && "
879 "cd ../pybindgen-src && "
880 "export PYTHONPATH=$PYTHONPATH:${BUILD}/target && "
881 "./waf configure --prefix=${BUILD}/target -d release && "
885 "mv -f ${BUILD}/target/lib/python*/site-packages/pybindgen ${BUILD}/target/. && "
886 "rm -rf ${BUILD}/target/lib && "
887 "cd ../passfd-src && "
888 "python setup.py build && "
889 "python setup.py install --install-lib ${BUILD}/target && "
890 "python setup.py clean && "
892 "./waf configure --prefix=${BUILD}/target --with-pybindgen=../pybindgen-src -d release --disable-examples --disable-tests && "
895 "rm -f ${BUILD}/target/lib/*.so && "
896 "cp -a ${BUILD}/ns3-src/build/release/libns3*.so ${BUILD}/target/lib && "
897 "cp -a ${BUILD}/ns3-src/build/release/bindings/python/ns ${BUILD}/target/lib &&"
901 pybindgen_source_url = server.shell_escape(pybindgen_source_url),
902 pygccxml_source_url = server.shell_escape(pygccxml_source_url),
903 ns3_source_url = server.shell_escape(ns3_source_url),
904 passfd_source_url = server.shell_escape(passfd_source_url),
907 # Just move ${BUILD}/target
911 " python -c 'import pygccxml, pybindgen, passfd' && "
912 " test -f lib/ns/_core.so && "
913 " test -f lib/ns/__init__.py && "
914 " test -f lib/ns/core.py && "
915 " test -f lib/libns3-core.so && "
916 " LD_LIBRARY_PATH=lib PYTHONPATH=lib python -c 'import ns.core' "
918 # Not working, reinstall
919 "test -d ${BUILD}/target && "
920 "[[ \"x\" != \"x$(find ${BUILD}/target -mindepth 1 -print -quit)\" ]] &&"
921 "( for i in ${BUILD}/target/* ; do rm -rf ${SOURCES}/${i##*/} ; done ) && " # mv doesn't like unclean targets
922 "mv -f ${BUILD}/target/* ${SOURCES}"
926 # Set extra environment paths
927 self.env['NEPI_NS3BINDINGS'] = "${SOURCES}/lib"
928 self.env['NEPI_NS3LIBRARY'] = "${SOURCES}/lib"
932 if self._tarball is None:
933 shared_tar = self._shared_nepi_tar and self._shared_nepi_tar()
934 if shared_tar is not None:
935 self._tarball = shared_tar
937 # Build an ad-hoc tarball
942 shared_tar = tempfile.NamedTemporaryFile(prefix='nepi-src-', suffix='.tar.gz')
944 proc = subprocess.Popen(
945 ["tar", "czf", shared_tar.name,
946 '-C', os.path.join(os.path.dirname(os.path.dirname(nepi.__file__)),'.'),
948 stdout = open("/dev/null","w"),
949 stdin = open("/dev/null","r"))
952 raise RuntimeError, "Failed to create nepi tarball"
954 self._tarball = self._shared_nepi_tar = shared_tar
958 class YumDependency(Dependency):
960 This dependency is an internal helper class used to
961 efficiently distribute yum-downloaded rpms.
963 It temporarily sets the yum cache as persistent in the
964 build master, and installs all the required packages.
966 The rpm packages left in the yum cache are gathered and
967 distributed by the underlying Dependency in an efficient
968 manner. Build slaves will then install those rpms back in
969 the cache before issuing the install command.
971 When packages have been installed already, nothing but an
972 empty tar is distributed.
975 # Class attribute holding a *weak* reference to the shared NEPI tar file
976 # so that they may share it. Don't operate on the file itself, it would
977 # be a mess, just use its path.
978 _shared_nepi_tar = None
980 def _build_get(self):
981 # canonical representation of dependencies
982 depends = ' '.join( sorted( (self.depends or "").split(' ') ) )
984 # download rpms and pack into a tar archive
986 "sudo -S nice yum -y makecache && "
987 "sudo -S sed -i -r 's/keepcache *= *0/keepcache=1/' /etc/yum.conf && "
989 "sudo -S nice yum -y install %s ; "
990 "rm -f ${BUILD}/packages.tar ; "
991 "tar -C /var/cache/yum -rf ${BUILD}/packages.tar $(cd /var/cache/yum ; find -iname '*.rpm')"
992 " ) || /bin/true ) && "
993 "sudo -S sed -i -r 's/keepcache *= *1/keepcache=0/' /etc/yum.conf && "
994 "( sudo -S nice yum -y clean packages || /bin/true ) "
996 def _build_set(self, value):
999 build = property(_build_get, _build_set)
1001 def _install_get(self):
1002 # canonical representation of dependencies
1003 depends = ' '.join( sorted( (self.depends or "").split(' ') ) )
1005 # unpack cached rpms into yum cache, install, and cleanup
1007 "sudo -S tar -k --keep-newer-files -C /var/cache/yum -xf packages.tar && "
1008 "sudo -S nice yum -y install %s && "
1009 "( sudo -S nice yum -y clean packages || /bin/true ) "
1011 def _install_set(self, value):
1014 install = property(_install_get, _install_set)