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)
399 (out, err), proc = self._popen_ssh_command(
400 "cat %(token_path)s" % {
401 'token_path' : os.path.join(self.home_path, 'build.token'),
406 if not proc.wait() and out:
407 slave_token = out.strip()
409 if slave_token != self._master_token:
410 # Get buildlog for the error message
412 (buildlog, err), proc = self._popen_ssh_command(
413 "cat %(buildlog)s" % {
414 'buildlog' : os.path.join(self.home_path, 'buildlog'),
415 'buildscript' : os.path.join(self.home_path, 'nepi-build.sh'),
422 raise RuntimeError, "Failed to set up application %s: "\
423 "build failed, got wrong token from pid %s/%s "\
424 "(expected %r, got %r), see buildlog: %s" % (
425 self.home_path, pid, ppid, self._master_token, slave_token, buildlog)
427 self._logger.info("Built %s", self)
429 def _do_kill_build(self):
430 pid = self._build_pid
431 ppid = self._build_ppid
434 self._logger.info("Killing build of %s", self)
437 host = self.node.hostname,
439 user = self.node.slicename,
441 ident_key = self.node.ident_path
445 def _do_build_master(self):
446 if not self.sources and not self.build and not self.buildDepends:
450 sources = self.sources.split(' ')
456 "%s@%s:%s" % (self.node.slicename, self.node.hostname,
457 os.path.join(self.home_path,'.'),)
459 except RuntimeError, e:
460 raise RuntimeError, "Failed upload source file %r: %s %s" \
461 % (sources, e.args[0], e.args[1],)
463 buildscript = cStringIO.StringIO()
465 if self.buildDepends:
466 # Install build dependencies
468 "sudo -S yum -y install %(packages)s\n" % {
469 'packages' : self.buildDepends
477 "mkdir -p build && ( cd build && ( %(command)s ) )\n" % {
478 'command' : self._replace_paths(self.build),
479 'home' : server.shell_escape(self.home_path),
484 buildscript.write("tar czf build.tar.gz build\n")
487 buildscript.write("echo %(master_token)s > build.token" % {
488 'master_token' : server.shell_escape(self._master_token)
495 def _do_install(self):
497 self._logger.info("Installing %s", self)
499 # Install application
501 self._popen_ssh_command(
502 "cd %(home)s && cd build && ( %(command)s ) > ${HOME}/%(home)s/installlog 2>&1 || ( tail ${HOME}/%(home)s/{install,build}log >&2 && false )" % \
504 'command' : self._replace_paths(self.install),
505 'home' : server.shell_escape(self.home_path),
508 except RuntimeError, e:
509 raise RuntimeError, "Failed install build sources: %s %s" % (e.args[0], e.args[1],)
511 def set_master(self, master):
512 self._master = master
514 def install_keys(self, prk, puk, passphrase):
516 self._master_passphrase = passphrase
517 self._master_prk = prk
518 self._master_puk = puk
519 self._master_prk_name = os.path.basename(prk.name)
520 self._master_puk_name = os.path.basename(puk.name)
522 def _do_install_keys(self):
523 prk = self._master_prk
524 puk = self._master_puk
528 [ prk.name, puk.name ],
529 '%s@%s:%s' % (self.node.slicename, self.node.hostname, self.home_path )
531 except RuntimeError, e:
532 raise RuntimeError, "Failed to set up application deployment keys: %s %s" \
533 % (e.args[0], e.args[1],)
537 cStringIO.StringIO('%s,%s %s\n' % (
538 self._master.node.hostname, socket.gethostbyname(self._master.node.hostname),
539 self._master.node.server_key)),
540 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
541 os.path.join(self.home_path,"master_known_hosts") )
543 except RuntimeError, e:
544 raise RuntimeError, "Failed to set up application deployment keys: %s %s" \
545 % (e.args[0], e.args[1],)
548 self._master_prk = None
549 self._master_puk = None
552 # make sure there's no leftover build processes
553 self._do_kill_build()
556 def _popen_scp(self, src, dst, retry = 3):
559 (out,err),proc = server.popen_scp(
564 ident_key = self.node.ident_path,
565 server_key = self.node.server_key
568 if server.eintr_retry(proc.wait)():
569 raise RuntimeError, (out, err)
570 return (out, err), proc
579 def _popen_ssh_command(self, command, retry = 0, noerrors=False, timeout=None):
580 (out,err),proc = server.popen_ssh_command(
582 host = self.node.hostname,
584 user = self.node.slicename,
586 ident_key = self.node.ident_path,
587 server_key = self.node.server_key,
592 if server.eintr_retry(proc.wait)():
594 raise RuntimeError, (out, err)
595 return (out, err), proc
597 class Application(Dependency):
599 An application also has dependencies, but also a command to be ran and monitored.
601 It adds the output of that command as traces.
604 TRACES = ('stdout','stderr','buildlog', 'output')
606 def __init__(self, api=None):
607 super(Application,self).__init__(api)
618 # Those are filled when the app is started
619 # Having both pid and ppid makes it harder
620 # for pid rollover to induce tracking mistakes
621 self._started = False
625 # Do not add to the python path of nodes
626 self.add_to_path = False
629 return "%s<command:%s%s>" % (
630 self.__class__.__name__,
631 "sudo " if self.sudo else "",
636 self._logger.info("Starting %s", self)
638 # Create shell script with the command
639 # This way, complex commands and scripts can be ran seamlessly
641 command = cStringIO.StringIO()
642 command.write('export PYTHONPATH=$PYTHONPATH:%s\n' % (
643 ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.node.pythonpath])
645 command.write('export PATH=$PATH:%s\n' % (
646 ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.node.pythonpath])
649 for envkey, envvals in self.node.env.iteritems():
650 for envval in envvals:
651 command.write('export %s=%s\n' % (envkey, envval))
652 command.write(self.command)
658 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
659 os.path.join(self.home_path, "app.sh"))
661 except RuntimeError, e:
662 raise RuntimeError, "Failed to set up application: %s %s" \
663 % (e.args[0], e.args[1],)
665 # Start process in a "daemonized" way, using nohup and heavy
666 # stdin/out redirection to avoid connection issues
667 (out,err),proc = rspawn.remote_spawn(
668 self._replace_paths("bash ./app.sh"),
671 home = self.home_path,
672 stdin = 'stdin' if self.stdin is not None else '/dev/null',
673 stdout = 'stdout' if self.stdout else '/dev/null',
674 stderr = 'stderr' if self.stderr else '/dev/null',
677 host = self.node.hostname,
679 user = self.node.slicename,
681 ident_key = self.node.ident_path,
682 server_key = self.node.server_key
686 raise RuntimeError, "Failed to set up application: %s %s" % (out,err,)
691 # Assuming the application is running on PlanetLab,
692 # proper pidfiles should be present at the app's home path.
693 # So we mark this application as started, and check the pidfiles
699 # NOTE: wait a bit for the pidfile to be created
700 if self._started and not self._pid or not self._ppid:
701 pidtuple = rspawn.remote_check_pid(
702 os.path.join(self.home_path,'pid'),
703 host = self.node.hostname,
705 user = self.node.slicename,
707 ident_key = self.node.ident_path,
708 server_key = self.node.server_key
712 self._pid, self._ppid = pidtuple
716 if not self._started:
717 return AS.STATUS_NOT_STARTED
718 elif not self._pid or not self._ppid:
719 return AS.STATUS_NOT_STARTED
721 status = rspawn.remote_status(
722 self._pid, self._ppid,
723 host = self.node.hostname,
725 user = self.node.slicename,
727 ident_key = self.node.ident_path,
728 server_key = self.node.server_key
731 if status is rspawn.NOT_STARTED:
732 return AS.STATUS_NOT_STARTED
733 elif status is rspawn.RUNNING:
734 return AS.STATUS_RUNNING
735 elif status is rspawn.FINISHED:
736 return AS.STATUS_FINISHED
739 return AS.STATUS_NOT_STARTED
742 status = self.status()
743 if status == AS.STATUS_RUNNING:
744 # kill by ppid+pid - SIGTERM first, then try SIGKILL
746 self._pid, self._ppid,
747 host = self.node.hostname,
749 user = self.node.slicename,
751 ident_key = self.node.ident_path,
752 server_key = self.node.server_key,
755 self._logger.info("Killed %s", self)
758 class NepiDependency(Dependency):
760 This dependency adds nepi itself to the python path,
761 so that you may run testbeds within PL nodes.
764 # Class attribute holding a *weak* reference to the shared NEPI tar file
765 # so that they may share it. Don't operate on the file itself, it would
766 # be a mess, just use its path.
767 _shared_nepi_tar = None
769 def __init__(self, api = None):
770 super(NepiDependency, self).__init__(api)
774 self.depends = 'python python-ipaddr python-setuptools'
776 # our sources are in our ad-hoc tarball
777 self.sources = self.tarball.name
779 tarname = os.path.basename(self.tarball.name)
781 # it's already built - just move the tarball into place
782 self.build = "mv -f ${SOURCES}/%s ." % (tarname,)
784 # unpack it into sources, and we're done
785 self.install = "tar xzf ${BUILD}/%s -C .." % (tarname,)
789 if self._tarball is None:
790 shared_tar = self._shared_nepi_tar and self._shared_nepi_tar()
791 if shared_tar is not None:
792 self._tarball = shared_tar
794 # Build an ad-hoc tarball
799 shared_tar = tempfile.NamedTemporaryFile(prefix='nepi-src-', suffix='.tar.gz')
801 proc = subprocess.Popen(
802 ["tar", "czf", shared_tar.name,
803 '-C', os.path.join(os.path.dirname(os.path.dirname(nepi.__file__)),'.'),
805 stdout = open("/dev/null","w"),
806 stdin = open("/dev/null","r"))
809 raise RuntimeError, "Failed to create nepi tarball"
811 self._tarball = self._shared_nepi_tar = shared_tar
815 class NS3Dependency(Dependency):
817 This dependency adds NS3 libraries to the library paths,
818 so that you may run the NS3 testbed within PL nodes.
820 You'll also need the NepiDependency.
823 def __init__(self, api = None):
824 super(NS3Dependency, self).__init__(api)
826 self.buildDepends = 'make waf gcc gcc-c++ gccxml unzip'
828 # We have to download the sources, untar, build...
829 pybindgen_source_url = "http://yans.pl.sophia.inria.fr/trac/nepi/raw-attachment/wiki/WikiStart/pybindgen-r794.tar.gz"
830 pygccxml_source_url = "http://leaseweb.dl.sourceforge.net/project/pygccxml/pygccxml/pygccxml-1.0/pygccxml-1.0.0.zip"
831 ns3_source_url = "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/ns-3.11-nepi/archive/tip.tar.gz"
832 passfd_source_url = "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/python-passfd/archive/tip.tar.gz"
836 " python -c 'import pygccxml, pybindgen, passfd' && "
837 " test -f lib/ns/_core.so && "
838 " test -f lib/ns/__init__.py && "
839 " test -f lib/ns/core.py && "
840 " test -f lib/libns3-core.so && "
841 " LD_LIBRARY_PATH=lib PYTHONPATH=lib python -c 'import ns.core' "
843 # Not working, rebuild
844 # Archive SHA1 sums to check
845 "echo '7158877faff2254e6c094bf18e6b4283cac19137 pygccxml-1.0.0.zip' > archive_sums.txt && "
846 "echo 'a18c2ccffd0df517bc37e2f3a2475092517c43f2 pybindgen-src.tar.gz' >> archive_sums.txt && "
847 " ( " # check existing files
848 " sha1sum -c archive_sums.txt && "
849 " test -f passfd-src.tar.gz && "
850 " test -f ns3-src.tar.gz "
851 " ) || ( " # nope? re-download
852 " rm -f pybindgen-src.zip pygccxml-1.0.0.zip passfd-src.tar.gz ns3-src.tar.gz && "
853 " wget -q -c -O pybindgen-src.tar.gz %(pybindgen_source_url)s && " # continue, to exploit the case when it has already been dl'ed
854 " wget -q -c -O pygccxml-1.0.0.zip %(pygccxml_source_url)s && "
855 " wget -q -c -O passfd-src.tar.gz %(passfd_source_url)s && "
856 " wget -q -c -O ns3-src.tar.gz %(ns3_source_url)s && "
857 " sha1sum -c archive_sums.txt " # Check SHA1 sums when applicable
859 "unzip -n pygccxml-1.0.0.zip && "
860 "mkdir -p pybindgen-src && "
861 "mkdir -p ns3-src && "
862 "mkdir -p passfd-src && "
863 "tar xzf ns3-src.tar.gz --strip-components=1 -C ns3-src && "
864 "tar xzf passfd-src.tar.gz --strip-components=1 -C passfd-src && "
865 "tar xzf pybindgen-src.tar.gz --strip-components=1 -C pybindgen-src && "
866 "rm -rf target && " # mv doesn't like unclean targets
867 "mkdir -p target && "
868 "cd pygccxml-1.0.0 && "
869 "rm -rf unittests docs && " # pygccxml has ~100M of unit tests - excessive - docs aren't needed either
870 "python setup.py build && "
871 "python setup.py install --install-lib ${BUILD}/target && "
872 "python setup.py clean && "
873 "cd ../pybindgen-src && "
874 "export PYTHONPATH=$PYTHONPATH:${BUILD}/target && "
875 "./waf configure --prefix=${BUILD}/target -d release && "
879 "mv -f ${BUILD}/target/lib/python*/site-packages/pybindgen ${BUILD}/target/. && "
880 "rm -rf ${BUILD}/target/lib && "
881 "cd ../passfd-src && "
882 "python setup.py build && "
883 "python setup.py install --install-lib ${BUILD}/target && "
884 "python setup.py clean && "
886 "./waf configure --prefix=${BUILD}/target --with-pybindgen=../pybindgen-src -d release --disable-examples --disable-tests --enable-threading && "
889 "rm -f ${BUILD}/target/lib/*.so && "
890 "cp -a ${BUILD}/ns3-src/build/release/libns3*.so ${BUILD}/target/lib && "
891 "cp -a ${BUILD}/ns3-src/build/release/bindings/python/ns ${BUILD}/target/lib &&"
895 pybindgen_source_url = server.shell_escape(pybindgen_source_url),
896 pygccxml_source_url = server.shell_escape(pygccxml_source_url),
897 ns3_source_url = server.shell_escape(ns3_source_url),
898 passfd_source_url = server.shell_escape(passfd_source_url),
901 # Just move ${BUILD}/target
905 " python -c 'import pygccxml, pybindgen, passfd' && "
906 " test -f lib/ns/_core.so && "
907 " test -f lib/ns/__init__.py && "
908 " test -f lib/ns/core.py && "
909 " test -f lib/libns3-core.so && "
910 " LD_LIBRARY_PATH=lib PYTHONPATH=lib python -c 'import ns.core' "
912 # Not working, reinstall
913 "test -d ${BUILD}/target && "
914 "[[ \"x\" != \"x$(find ${BUILD}/target -mindepth 1 -print -quit)\" ]] &&"
915 "( for i in ${BUILD}/target/* ; do rm -rf ${SOURCES}/${i##*/} ; done ) && " # mv doesn't like unclean targets
916 "mv -f ${BUILD}/target/* ${SOURCES}"
920 # Set extra environment paths
921 self.env['NEPI_NS3BINDINGS'] = "${SOURCES}/lib"
922 self.env['NEPI_NS3LIBRARY'] = "${SOURCES}/lib"
926 if self._tarball is None:
927 shared_tar = self._shared_nepi_tar and self._shared_nepi_tar()
928 if shared_tar is not None:
929 self._tarball = shared_tar
931 # Build an ad-hoc tarball
936 shared_tar = tempfile.NamedTemporaryFile(prefix='nepi-src-', suffix='.tar.gz')
938 proc = subprocess.Popen(
939 ["tar", "czf", shared_tar.name,
940 '-C', os.path.join(os.path.dirname(os.path.dirname(nepi.__file__)),'.'),
942 stdout = open("/dev/null","w"),
943 stdin = open("/dev/null","r"))
946 raise RuntimeError, "Failed to create nepi tarball"
948 self._tarball = self._shared_nepi_tar = shared_tar
952 class YumDependency(Dependency):
954 This dependency is an internal helper class used to
955 efficiently distribute yum-downloaded rpms.
957 It temporarily sets the yum cache as persistent in the
958 build master, and installs all the required packages.
960 The rpm packages left in the yum cache are gathered and
961 distributed by the underlying Dependency in an efficient
962 manner. Build slaves will then install those rpms back in
963 the cache before issuing the install command.
965 When packages have been installed already, nothing but an
966 empty tar is distributed.
969 # Class attribute holding a *weak* reference to the shared NEPI tar file
970 # so that they may share it. Don't operate on the file itself, it would
971 # be a mess, just use its path.
972 _shared_nepi_tar = None
974 def _build_get(self):
975 # canonical representation of dependencies
976 depends = ' '.join( sorted( (self.depends or "").split(' ') ) )
978 # download rpms and pack into a tar archive
980 "sudo -S nice yum -y makecache && "
981 "sudo -S sed -i -r 's/keepcache *= *0/keepcache=1/' /etc/yum.conf && "
983 "sudo -S nice yum -y install %s ; "
984 "rm -f ${BUILD}/packages.tar ; "
985 "tar -C /var/cache/yum -rf ${BUILD}/packages.tar $(cd /var/cache/yum ; find -iname '*.rpm')"
986 " ) || /bin/true ) && "
987 "sudo -S sed -i -r 's/keepcache *= *1/keepcache=0/' /etc/yum.conf && "
988 "sudo -S nice yum -y clean packages "
990 def _build_set(self, value):
993 build = property(_build_get, _build_set)
995 def _install_get(self):
996 # canonical representation of dependencies
997 depends = ' '.join( sorted( (self.depends or "").split(' ') ) )
999 # unpack cached rpms into yum cache, install, and cleanup
1001 "sudo -S tar -k --keep-newer-files -C /var/cache/yum -xf packages.tar && "
1002 "sudo -S nice yum -y install %s && "
1003 "sudo -S nice yum -y clean packages "
1005 def _install_set(self, value):
1008 install = property(_install_get, _install_set)