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 sync_trace(self, local_dir, whichtrace):
116 tracefile = self.remote_trace_path(whichtrace)
120 local_path = os.path.join(local_dir, tracefile)
122 # create parent local folders
123 proc = subprocess.Popen(
124 ["mkdir", "-p", os.path.dirname(local_path)],
125 stdout = open("/dev/null","w"),
126 stdin = open("/dev/null","r"))
129 raise RuntimeError, "Failed to synchronize trace"
134 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
138 except RuntimeError, e:
139 raise RuntimeError, "Failed to synchronize trace: %s %s" \
140 % (e.args[0], e.args[1],)
145 # We assume a correct deployment, so recovery only
146 # means we mark this dependency as deployed
150 self._logger.info("Setting up %s", self)
156 def async_setup(self):
157 if not self._setuper:
162 self._setuper._exc.append(sys.exc_info())
163 self._setuper = threading.Thread(
165 self._setuper._exc = []
166 self._setuper.start()
168 def async_setup_wait(self):
170 self._logger.info("Waiting for %s to be setup", self)
174 if self._setuper._exc:
175 exctyp,exval,exctrace = self._setuper._exc[0]
176 raise exctyp,exval,exctrace
178 raise RuntimeError, "Failed to setup application"
182 def _make_home(self):
183 # Make sure all the paths are created where
184 # they have to be created for deployment
187 self._popen_ssh_command(
188 "mkdir -p %(home)s && ( rm -f %(home)s/{pid,build-pid,nepi-build.sh} >/dev/null 2>&1 || /bin/true )" \
189 % { 'home' : server.shell_escape(self.home_path) }
191 except RuntimeError, e:
192 raise RuntimeError, "Failed to set up application %s: %s %s" % (self.home_path, e.args[0], e.args[1],)
195 # Write program input
198 cStringIO.StringIO(self.stdin),
199 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
200 os.path.join(self.home_path, 'stdin') ),
202 except RuntimeError, e:
203 raise RuntimeError, "Failed to set up application %s: %s %s" \
204 % (self.home_path, e.args[0], e.args[1],)
206 def _replace_paths(self, command):
208 Replace all special path tags with shell-escaped actual paths.
210 # need to append ${HOME} if paths aren't absolute, to MAKE them absolute.
211 root = '' if self.home_path.startswith('/') else "${HOME}/"
213 .replace("${SOURCES}", root+server.shell_escape(self.home_path))
214 .replace("${BUILD}", root+server.shell_escape(os.path.join(self.home_path,'build'))) )
216 def _launch_build(self):
217 if self._master is not None:
218 self._do_install_keys()
219 buildscript = self._do_build_slave()
221 buildscript = self._do_build_master()
223 if buildscript is not None:
224 self._logger.info("Building %s", self)
226 # upload build script
230 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
231 os.path.join(self.home_path, 'nepi-build.sh') )
233 except RuntimeError, e:
234 raise RuntimeError, "Failed to set up application %s: %s %s" \
235 % (self.home_path, e.args[0], e.args[1],)
238 self._do_launch_build()
240 def _finish_build(self):
241 self._do_wait_build()
244 def _do_build_slave(self):
245 if not self.sources and not self.build:
248 # Create build script
252 sources = self.sources.split(' ')
254 "%s@%s:%s" % (self._master.node.slicename, self._master.node.hostname,
255 os.path.join(self._master.home_path, os.path.basename(source)),)
256 for source in sources
261 "%s@%s:%s" % (self._master.node.slicename, self._master.node.hostname,
262 os.path.join(self._master.home_path, 'build.tar.gz'),)
265 launch_agent = "{ ( echo -e '#!/bin/sh\\ncat' > .ssh-askpass ) && chmod u+x .ssh-askpass"\
266 " && export SSH_ASKPASS=$(pwd)/.ssh-askpass "\
267 " && ssh-agent > .ssh-agent.sh ; } && . ./.ssh-agent.sh && ( echo $NEPI_MASTER_PASSPHRASE | ssh-add %(prk)s ) && rm -rf %(prk)s %(puk)s" % \
269 'prk' : server.shell_escape(self._master_prk_name),
270 'puk' : server.shell_escape(self._master_puk_name),
273 kill_agent = "kill $SSH_AGENT_PID"
275 waitmaster = "{ . ./.ssh-agent.sh ; while [[ $(ssh -q -o UserKnownHostsFile=%(hostkey)s %(master)s cat %(token_path)s) != %(token)s ]] ; do sleep 5 ; done ; }" % {
276 'hostkey' : 'master_known_hosts',
277 'master' : "%s@%s" % (self._master.node.slicename, self._master.node.hostname),
278 'token_path' : os.path.join(self._master.home_path, 'build.token'),
279 'token' : server.shell_escape(self._master._master_token),
282 syncfiles = "scp -p -o UserKnownHostsFile=%(hostkey)s %(files)s ." % {
283 'hostkey' : 'master_known_hosts',
284 'files' : ' '.join(files),
287 syncfiles += " && tar xzf build.tar.gz"
288 syncfiles += " && ( echo %s > build.token )" % (server.shell_escape(self._master_token),)
289 syncfiles = "{ . ./.ssh-agent.sh ; %s ; }" % (syncfiles,)
291 cleanup = "{ . ./.ssh-agent.sh ; kill $SSH_AGENT_PID ; rm -rf %(prk)s %(puk)s master_known_hosts .ssh-askpass ; }" % {
292 'prk' : server.shell_escape(self._master_prk_name),
293 'puk' : server.shell_escape(self._master_puk_name),
296 slavescript = "( ( %(launch_agent)s && %(waitmaster)s && %(syncfiles)s && %(kill_agent)s && %(cleanup)s ) || %(cleanup)s )" % {
297 'waitmaster' : waitmaster,
298 'syncfiles' : syncfiles,
300 'kill_agent' : kill_agent,
301 'launch_agent' : launch_agent,
302 'home' : server.shell_escape(self.home_path),
305 return cStringIO.StringIO(slavescript)
307 def _do_launch_build(self):
308 script = "bash ./nepi-build.sh"
309 if self._master_passphrase:
310 script = "NEPI_MASTER_PASSPHRASE=%s %s" % (
311 server.shell_escape(self._master_passphrase),
314 (out,err),proc = rspawn.remote_spawn(
316 pidfile = 'build-pid',
317 home = self.home_path,
320 stderr = rspawn.STDOUT,
322 host = self.node.hostname,
324 user = self.node.slicename,
326 ident_key = self.node.ident_path,
327 server_key = self.node.server_key
331 raise RuntimeError, "Failed to set up build slave %s: %s %s" % (self.home_path, out,err,)
337 pidtuple = rspawn.remote_check_pid(
338 os.path.join(self.home_path,'build-pid'),
339 host = self.node.hostname,
341 user = self.node.slicename,
343 ident_key = self.node.ident_path,
344 server_key = self.node.server_key
349 self._build_pid, self._build_ppid = pidtuple
353 delay = min(30,delay*1.2)
355 raise RuntimeError, "Failed to set up build slave %s: cannot get pid" % (self.home_path,)
357 self._logger.info("Deploying %s", self)
359 def _do_wait_build(self):
360 pid = self._build_pid
361 ppid = self._build_ppid
367 status = rspawn.remote_status(
369 host = self.node.hostname,
371 user = self.node.slicename,
373 ident_key = self.node.ident_path,
374 server_key = self.node.server_key
377 if status is not rspawn.RUNNING:
378 self._build_pid = self._build_ppid = None
382 self._logger.info("Waiting for %s to finish building %s", self,
383 "(build slave)" if self._master is not None else "(build master)")
386 time.sleep(delay*(0.5+random.random()))
387 delay = min(30,delay*1.2)
390 (out, err), proc = self._popen_ssh_command(
391 "cat %(token_path)s" % {
392 'token_path' : os.path.join(self.home_path, 'build.token'),
396 if not proc.wait() and out:
397 slave_token = out.strip()
399 if slave_token != self._master_token:
400 # Get buildlog for the error message
402 (buildlog, err), proc = self._popen_ssh_command(
403 "cat %(buildlog)s" % {
404 'buildlog' : os.path.join(self.home_path, 'buildlog'),
405 'buildscript' : os.path.join(self.home_path, 'nepi-build.sh'),
411 raise RuntimeError, "Failed to set up application %s: "\
412 "build failed, got wrong token from pid %s/%s "\
413 "(expected %r, got %r), see buildlog: %s" % (
414 self.home_path, pid, ppid, self._master_token, slave_token, buildlog)
416 self._logger.info("Built %s", self)
418 def _do_kill_build(self):
419 pid = self._build_pid
420 ppid = self._build_ppid
423 self._logger.info("Killing build of %s", self)
426 host = self.node.hostname,
428 user = self.node.slicename,
430 ident_key = self.node.ident_path
434 def _do_build_master(self):
435 if not self.sources and not self.build and not self.buildDepends:
439 sources = self.sources.split(' ')
445 "%s@%s:%s" % (self.node.slicename, self.node.hostname,
446 os.path.join(self.home_path,'.'),)
448 except RuntimeError, e:
449 raise RuntimeError, "Failed upload source file %r: %s %s" \
450 % (sources, e.args[0], e.args[1],)
452 buildscript = cStringIO.StringIO()
454 if self.buildDepends:
455 # Install build dependencies
457 "sudo -S yum -y install %(packages)s\n" % {
458 'packages' : self.buildDepends
466 "mkdir -p build && ( cd build && ( %(command)s ) )\n" % {
467 'command' : self._replace_paths(self.build),
468 'home' : server.shell_escape(self.home_path),
473 buildscript.write("tar czf build.tar.gz build\n")
476 buildscript.write("echo %(master_token)s > build.token" % {
477 'master_token' : server.shell_escape(self._master_token)
484 def _do_install(self):
486 self._logger.info("Installing %s", self)
488 # Install application
490 self._popen_ssh_command(
491 "cd %(home)s && cd build && ( %(command)s ) > ${HOME}/%(home)s/installlog 2>&1 || ( tail ${HOME}/%(home)s/{install,build}log >&2 && false )" % \
493 'command' : self._replace_paths(self.install),
494 'home' : server.shell_escape(self.home_path),
497 except RuntimeError, e:
498 raise RuntimeError, "Failed install build sources: %s %s" % (e.args[0], e.args[1],)
500 def set_master(self, master):
501 self._master = master
503 def install_keys(self, prk, puk, passphrase):
505 self._master_passphrase = passphrase
506 self._master_prk = prk
507 self._master_puk = puk
508 self._master_prk_name = os.path.basename(prk.name)
509 self._master_puk_name = os.path.basename(puk.name)
511 def _do_install_keys(self):
512 prk = self._master_prk
513 puk = self._master_puk
517 [ prk.name, puk.name ],
518 '%s@%s:%s' % (self.node.slicename, self.node.hostname, self.home_path )
520 except RuntimeError, e:
521 raise RuntimeError, "Failed to set up application deployment keys: %s %s" \
522 % (e.args[0], e.args[1],)
526 cStringIO.StringIO('%s,%s %s\n' % (
527 self._master.node.hostname, socket.gethostbyname(self._master.node.hostname),
528 self._master.node.server_key)),
529 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
530 os.path.join(self.home_path,"master_known_hosts") )
532 except RuntimeError, e:
533 raise RuntimeError, "Failed to set up application deployment keys: %s %s" \
534 % (e.args[0], e.args[1],)
537 self._master_prk = None
538 self._master_puk = None
541 # make sure there's no leftover build processes
542 self._do_kill_build()
545 def _popen_scp(self, src, dst, retry = True):
546 (out,err),proc = server.popen_scp(
551 ident_key = self.node.ident_path,
552 server_key = self.node.server_key
555 if server.eintr_retry(proc.wait)():
556 raise RuntimeError, (out, err)
557 return (out, err), proc
561 def _popen_ssh_command(self, command, retry = True, noerrors=False):
562 (out,err),proc = server.popen_ssh_command(
564 host = self.node.hostname,
566 user = self.node.slicename,
568 ident_key = self.node.ident_path,
569 server_key = self.node.server_key
572 if server.eintr_retry(proc.wait)():
574 raise RuntimeError, (out, err)
575 return (out, err), proc
577 class Application(Dependency):
579 An application also has dependencies, but also a command to be ran and monitored.
581 It adds the output of that command as traces.
584 TRACES = ('stdout','stderr','buildlog')
586 def __init__(self, api=None):
587 super(Application,self).__init__(api)
597 # Those are filled when the app is started
598 # Having both pid and ppid makes it harder
599 # for pid rollover to induce tracking mistakes
600 self._started = False
604 # Do not add to the python path of nodes
605 self.add_to_path = False
608 return "%s<command:%s%s>" % (
609 self.__class__.__name__,
610 "sudo " if self.sudo else "",
615 self._logger.info("Starting %s", self)
617 # Create shell script with the command
618 # This way, complex commands and scripts can be ran seamlessly
620 command = cStringIO.StringIO()
621 command.write('export PYTHONPATH=$PYTHONPATH:%s\n' % (
622 ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.node.pythonpath])
624 command.write('export PATH=$PATH:%s\n' % (
625 ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.node.pythonpath])
628 for envkey, envvals in self.node.env.iteritems():
629 for envval in envvals:
630 command.write('export %s=%s\n' % (envkey, envval))
631 command.write(self.command)
637 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
638 os.path.join(self.home_path, "app.sh"))
640 except RuntimeError, e:
641 raise RuntimeError, "Failed to set up application: %s %s" \
642 % (e.args[0], e.args[1],)
644 # Start process in a "daemonized" way, using nohup and heavy
645 # stdin/out redirection to avoid connection issues
646 (out,err),proc = rspawn.remote_spawn(
647 self._replace_paths("bash ./app.sh"),
650 home = self.home_path,
651 stdin = 'stdin' if self.stdin is not None else '/dev/null',
652 stdout = 'stdout' if self.stdout else '/dev/null',
653 stderr = 'stderr' if self.stderr else '/dev/null',
656 host = self.node.hostname,
658 user = self.node.slicename,
660 ident_key = self.node.ident_path,
661 server_key = self.node.server_key
665 raise RuntimeError, "Failed to set up application: %s %s" % (out,err,)
670 # Assuming the application is running on PlanetLab,
671 # proper pidfiles should be present at the app's home path.
672 # So we mark this application as started, and check the pidfiles
678 # NOTE: wait a bit for the pidfile to be created
679 if self._started and not self._pid or not self._ppid:
680 pidtuple = rspawn.remote_check_pid(
681 os.path.join(self.home_path,'pid'),
682 host = self.node.hostname,
684 user = self.node.slicename,
686 ident_key = self.node.ident_path,
687 server_key = self.node.server_key
691 self._pid, self._ppid = pidtuple
695 if not self._started:
696 return AS.STATUS_NOT_STARTED
697 elif not self._pid or not self._ppid:
698 return AS.STATUS_NOT_STARTED
700 status = rspawn.remote_status(
701 self._pid, self._ppid,
702 host = self.node.hostname,
704 user = self.node.slicename,
706 ident_key = self.node.ident_path,
707 server_key = self.node.server_key
710 if status is rspawn.NOT_STARTED:
711 return AS.STATUS_NOT_STARTED
712 elif status is rspawn.RUNNING:
713 return AS.STATUS_RUNNING
714 elif status is rspawn.FINISHED:
715 return AS.STATUS_FINISHED
718 return AS.STATUS_NOT_STARTED
721 status = self.status()
722 if status == AS.STATUS_RUNNING:
723 # kill by ppid+pid - SIGTERM first, then try SIGKILL
725 self._pid, self._ppid,
726 host = self.node.hostname,
728 user = self.node.slicename,
730 ident_key = self.node.ident_path,
731 server_key = self.node.server_key,
734 self._logger.info("Killed %s", self)
737 class NepiDependency(Dependency):
739 This dependency adds nepi itself to the python path,
740 so that you may run testbeds within PL nodes.
743 # Class attribute holding a *weak* reference to the shared NEPI tar file
744 # so that they may share it. Don't operate on the file itself, it would
745 # be a mess, just use its path.
746 _shared_nepi_tar = None
748 def __init__(self, api = None):
749 super(NepiDependency, self).__init__(api)
753 self.depends = 'python python-ipaddr python-setuptools'
755 # our sources are in our ad-hoc tarball
756 self.sources = self.tarball.name
758 tarname = os.path.basename(self.tarball.name)
760 # it's already built - just move the tarball into place
761 self.build = "mv -f ${SOURCES}/%s ." % (tarname,)
763 # unpack it into sources, and we're done
764 self.install = "tar xzf ${BUILD}/%s -C .." % (tarname,)
768 if self._tarball is None:
769 shared_tar = self._shared_nepi_tar and self._shared_nepi_tar()
770 if shared_tar is not None:
771 self._tarball = shared_tar
773 # Build an ad-hoc tarball
778 shared_tar = tempfile.NamedTemporaryFile(prefix='nepi-src-', suffix='.tar.gz')
780 proc = subprocess.Popen(
781 ["tar", "czf", shared_tar.name,
782 '-C', os.path.join(os.path.dirname(os.path.dirname(nepi.__file__)),'.'),
784 stdout = open("/dev/null","w"),
785 stdin = open("/dev/null","r"))
788 raise RuntimeError, "Failed to create nepi tarball"
790 self._tarball = self._shared_nepi_tar = shared_tar
794 class NS3Dependency(Dependency):
796 This dependency adds NS3 libraries to the library paths,
797 so that you may run the NS3 testbed within PL nodes.
799 You'll also need the NepiDependency.
802 def __init__(self, api = None):
803 super(NS3Dependency, self).__init__(api)
805 self.buildDepends = 'make waf gcc gcc-c++ gccxml unzip'
807 # We have to download the sources, untar, build...
808 pybindgen_source_url = "http://yans.pl.sophia.inria.fr/trac/nepi/raw-attachment/wiki/WikiStart/pybindgen-r794.tar.gz"
809 pygccxml_source_url = "http://leaseweb.dl.sourceforge.net/project/pygccxml/pygccxml/pygccxml-1.0/pygccxml-1.0.0.zip"
810 ns3_source_url = "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/ns-3.11-nepi/archive/tip.tar.gz"
811 passfd_source_url = "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/python-passfd/archive/tip.tar.gz"
815 " python -c 'import pygccxml, pybindgen, passfd' && "
816 " test -f lib/ns/_core.so && "
817 " test -f lib/ns/__init__.py && "
818 " test -f lib/ns/core.py && "
819 " test -f lib/libns3-core.so && "
820 " LD_LIBRARY_PATH=lib PYTHONPATH=lib python -c 'import ns.core' "
822 # Not working, rebuild
823 # Archive SHA1 sums to check
824 "echo '7158877faff2254e6c094bf18e6b4283cac19137 pygccxml-1.0.0.zip' > archive_sums.txt && "
825 "echo 'a18c2ccffd0df517bc37e2f3a2475092517c43f2 pybindgen-src.tar.gz' >> archive_sums.txt && "
826 " ( " # check existing files
827 " sha1sum -c archive_sums.txt && "
828 " test -f passfd-src.tar.gz && "
829 " test -f ns3-src.tar.gz "
830 " ) || ( " # nope? re-download
831 " rm -f pybindgen-src.zip pygccxml-1.0.0.zip passfd-src.tar.gz ns3-src.tar.gz && "
832 " wget -q -c -O pybindgen-src.tar.gz %(pybindgen_source_url)s && " # continue, to exploit the case when it has already been dl'ed
833 " wget -q -c -O pygccxml-1.0.0.zip %(pygccxml_source_url)s && "
834 " wget -q -c -O passfd-src.tar.gz %(passfd_source_url)s && "
835 " wget -q -c -O ns3-src.tar.gz %(ns3_source_url)s && "
836 " sha1sum -c archive_sums.txt " # Check SHA1 sums when applicable
838 "unzip -n pygccxml-1.0.0.zip && "
839 "mkdir -p pybindgen-src && "
840 "mkdir -p ns3-src && "
841 "mkdir -p passfd-src && "
842 "tar xzf ns3-src.tar.gz --strip-components=1 -C ns3-src && "
843 "tar xzf passfd-src.tar.gz --strip-components=1 -C passfd-src && "
844 "tar xzf pybindgen-src.tar.gz --strip-components=1 -C pybindgen-src && "
845 "rm -rf target && " # mv doesn't like unclean targets
846 "mkdir -p target && "
847 "cd pygccxml-1.0.0 && "
848 "rm -rf unittests docs && " # pygccxml has ~100M of unit tests - excessive - docs aren't needed either
849 "python setup.py build && "
850 "python setup.py install --install-lib ${BUILD}/target && "
851 "python setup.py clean && "
852 "cd ../pybindgen-src && "
853 "export PYTHONPATH=$PYTHONPATH:${BUILD}/target && "
854 "./waf configure --prefix=${BUILD}/target -d release && "
858 "mv -f ${BUILD}/target/lib/python*/site-packages/pybindgen ${BUILD}/target/. && "
859 "rm -rf ${BUILD}/target/lib && "
860 "cd ../passfd-src && "
861 "python setup.py build && "
862 "python setup.py install --install-lib ${BUILD}/target && "
863 "python setup.py clean && "
865 "./waf configure --prefix=${BUILD}/target --with-pybindgen=../pybindgen-src -d release --disable-examples --disable-tests --enable-threading && "
868 "rm -f ${BUILD}/target/lib/*.so && "
869 "cp -a ${BUILD}/ns3-src/build/release/libns3*.so ${BUILD}/target/lib && "
870 "cp -a ${BUILD}/ns3-src/build/release/bindings/python/ns ${BUILD}/target/lib &&"
874 pybindgen_source_url = server.shell_escape(pybindgen_source_url),
875 pygccxml_source_url = server.shell_escape(pygccxml_source_url),
876 ns3_source_url = server.shell_escape(ns3_source_url),
877 passfd_source_url = server.shell_escape(passfd_source_url),
880 # Just move ${BUILD}/target
884 " python -c 'import pygccxml, pybindgen, passfd' && "
885 " test -f lib/ns/_core.so && "
886 " test -f lib/ns/__init__.py && "
887 " test -f lib/ns/core.py && "
888 " test -f lib/libns3-core.so && "
889 " LD_LIBRARY_PATH=lib PYTHONPATH=lib python -c 'import ns.core' "
891 # Not working, reinstall
892 "test -d ${BUILD}/target && "
893 "[[ \"x\" != \"x$(find ${BUILD}/target -mindepth 1 -print -quit)\" ]] &&"
894 "( for i in ${BUILD}/target/* ; do rm -rf ${SOURCES}/${i##*/} ; done ) && " # mv doesn't like unclean targets
895 "mv -f ${BUILD}/target/* ${SOURCES}"
899 # Set extra environment paths
900 self.env['NEPI_NS3BINDINGS'] = "${SOURCES}/lib"
901 self.env['NEPI_NS3LIBRARY'] = "${SOURCES}/lib"
905 if self._tarball is None:
906 shared_tar = self._shared_nepi_tar and self._shared_nepi_tar()
907 if shared_tar is not None:
908 self._tarball = shared_tar
910 # Build an ad-hoc tarball
915 shared_tar = tempfile.NamedTemporaryFile(prefix='nepi-src-', suffix='.tar.gz')
917 proc = subprocess.Popen(
918 ["tar", "czf", shared_tar.name,
919 '-C', os.path.join(os.path.dirname(os.path.dirname(nepi.__file__)),'.'),
921 stdout = open("/dev/null","w"),
922 stdin = open("/dev/null","r"))
925 raise RuntimeError, "Failed to create nepi tarball"
927 self._tarball = self._shared_nepi_tar = shared_tar
931 class YumDependency(Dependency):
933 This dependency is an internal helper class used to
934 efficiently distribute yum-downloaded rpms.
936 It temporarily sets the yum cache as persistent in the
937 build master, and installs all the required packages.
939 The rpm packages left in the yum cache are gathered and
940 distributed by the underlying Dependency in an efficient
941 manner. Build slaves will then install those rpms back in
942 the cache before issuing the install command.
944 When packages have been installed already, nothing but an
945 empty tar is distributed.
948 # Class attribute holding a *weak* reference to the shared NEPI tar file
949 # so that they may share it. Don't operate on the file itself, it would
950 # be a mess, just use its path.
951 _shared_nepi_tar = None
953 def _build_get(self):
954 # canonical representation of dependencies
955 depends = ' '.join( sorted( (self.depends or "").split(' ') ) )
957 # download rpms and pack into a tar archive
959 "sudo -S nice yum -y makecache && "
960 "sudo -S sed -i -r 's/keepcache *= *0/keepcache=1/' /etc/yum.conf && "
962 "sudo -S nice yum -y install %s ; "
963 "rm -f ${BUILD}/packages.tar ; "
964 "tar -C /var/cache/yum -rf ${BUILD}/packages.tar $(cd /var/cache/yum ; find -iname '*.rpm')"
965 " ) || /bin/true ) && "
966 "sudo -S sed -i -r 's/keepcache *= *1/keepcache=0/' /etc/yum.conf && "
967 "sudo -S nice yum -y clean packages "
969 def _build_set(self, value):
972 build = property(_build_get, _build_set)
974 def _install_get(self):
975 # canonical representation of dependencies
976 depends = ' '.join( sorted( (self.depends or "").split(' ') ) )
978 # unpack cached rpms into yum cache, install, and cleanup
980 "sudo -S tar -k --keep-newer-files -C /var/cache/yum -xf packages.tar && "
981 "sudo -S nice yum -y install %s && "
982 "sudo -S nice yum -y clean packages "
984 def _install_set(self, value):
987 install = property(_install_get, _install_set)