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 self._logger.info("Setting up %s", self)
151 def async_setup(self):
152 if not self._setuper:
157 self._setuper._exc.append(sys.exc_info())
158 self._setuper = threading.Thread(
160 self._setuper._exc = []
161 self._setuper.start()
163 def async_setup_wait(self):
165 self._logger.info("Waiting for %s to be setup", self)
169 if self._setuper._exc:
170 exctyp,exval,exctrace = self._setuper._exc[0]
171 raise exctyp,exval,exctrace
173 raise RuntimeError, "Failed to setup application"
177 def _make_home(self):
178 # Make sure all the paths are created where
179 # they have to be created for deployment
182 self._popen_ssh_command(
183 "mkdir -p %(home)s && ( rm -f %(home)s/{pid,build-pid,nepi-build.sh} >/dev/null 2>&1 || /bin/true )" \
184 % { 'home' : server.shell_escape(self.home_path) }
186 except RuntimeError, e:
187 raise RuntimeError, "Failed to set up application %s: %s %s" % (self.home_path, e.args[0], e.args[1],)
190 # Write program input
193 cStringIO.StringIO(self.stdin),
194 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
195 os.path.join(self.home_path, 'stdin') ),
197 except RuntimeError, e:
198 raise RuntimeError, "Failed to set up application %s: %s %s" \
199 % (self.home_path, e.args[0], e.args[1],)
201 def _replace_paths(self, command):
203 Replace all special path tags with shell-escaped actual paths.
205 # need to append ${HOME} if paths aren't absolute, to MAKE them absolute.
206 root = '' if self.home_path.startswith('/') else "${HOME}/"
208 .replace("${SOURCES}", root+server.shell_escape(self.home_path))
209 .replace("${BUILD}", root+server.shell_escape(os.path.join(self.home_path,'build'))) )
211 def _launch_build(self):
212 if self._master is not None:
213 self._do_install_keys()
214 buildscript = self._do_build_slave()
216 buildscript = self._do_build_master()
218 if buildscript is not None:
219 self._logger.info("Building %s", self)
221 # upload build script
225 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
226 os.path.join(self.home_path, 'nepi-build.sh') )
228 except RuntimeError, e:
229 raise RuntimeError, "Failed to set up application %s: %s %s" \
230 % (self.home_path, e.args[0], e.args[1],)
233 self._do_launch_build()
235 def _finish_build(self):
236 self._do_wait_build()
239 def _do_build_slave(self):
240 if not self.sources and not self.build:
243 # Create build script
247 sources = self.sources.split(' ')
249 "%s@%s:%s" % (self._master.node.slicename, self._master.node.hostname,
250 os.path.join(self._master.home_path, os.path.basename(source)),)
251 for source in sources
256 "%s@%s:%s" % (self._master.node.slicename, self._master.node.hostname,
257 os.path.join(self._master.home_path, 'build.tar.gz'),)
260 launch_agent = "{ ( echo -e '#!/bin/sh\\ncat' > .ssh-askpass ) && chmod u+x .ssh-askpass"\
261 " && export SSH_ASKPASS=$(pwd)/.ssh-askpass "\
262 " && ssh-agent > .ssh-agent.sh ; } && . ./.ssh-agent.sh && ( echo $NEPI_MASTER_PASSPHRASE | ssh-add %(prk)s ) && rm -rf %(prk)s %(puk)s" % \
264 'prk' : server.shell_escape(self._master_prk_name),
265 'puk' : server.shell_escape(self._master_puk_name),
268 kill_agent = "kill $SSH_AGENT_PID"
270 waitmaster = "{ . ./.ssh-agent.sh ; while [[ $(ssh -q -o UserKnownHostsFile=%(hostkey)s %(master)s cat %(token_path)s) != %(token)s ]] ; do sleep 5 ; done ; }" % {
271 'hostkey' : 'master_known_hosts',
272 'master' : "%s@%s" % (self._master.node.slicename, self._master.node.hostname),
273 'token_path' : os.path.join(self._master.home_path, 'build.token'),
274 'token' : server.shell_escape(self._master._master_token),
277 syncfiles = "scp -p -o UserKnownHostsFile=%(hostkey)s %(files)s ." % {
278 'hostkey' : 'master_known_hosts',
279 'files' : ' '.join(files),
282 syncfiles += " && tar xzf build.tar.gz"
283 syncfiles += " && ( echo %s > build.token )" % (server.shell_escape(self._master_token),)
284 syncfiles = "{ . ./.ssh-agent.sh ; %s ; }" % (syncfiles,)
286 cleanup = "{ . ./.ssh-agent.sh ; kill $SSH_AGENT_PID ; rm -rf %(prk)s %(puk)s master_known_hosts .ssh-askpass ; }" % {
287 'prk' : server.shell_escape(self._master_prk_name),
288 'puk' : server.shell_escape(self._master_puk_name),
291 slavescript = "( ( %(launch_agent)s && %(waitmaster)s && %(syncfiles)s && %(kill_agent)s && %(cleanup)s ) || %(cleanup)s )" % {
292 'waitmaster' : waitmaster,
293 'syncfiles' : syncfiles,
295 'kill_agent' : kill_agent,
296 'launch_agent' : launch_agent,
297 'home' : server.shell_escape(self.home_path),
300 return cStringIO.StringIO(slavescript)
302 def _do_launch_build(self):
303 script = "bash ./nepi-build.sh"
304 if self._master_passphrase:
305 script = "NEPI_MASTER_PASSPHRASE=%s %s" % (
306 server.shell_escape(self._master_passphrase),
309 (out,err),proc = rspawn.remote_spawn(
311 pidfile = 'build-pid',
312 home = self.home_path,
315 stderr = rspawn.STDOUT,
317 host = self.node.hostname,
319 user = self.node.slicename,
321 ident_key = self.node.ident_path,
322 server_key = self.node.server_key
326 raise RuntimeError, "Failed to set up build slave %s: %s %s" % (self.home_path, out,err,)
332 pidtuple = rspawn.remote_check_pid(
333 os.path.join(self.home_path,'build-pid'),
334 host = self.node.hostname,
336 user = self.node.slicename,
338 ident_key = self.node.ident_path,
339 server_key = self.node.server_key
344 self._build_pid, self._build_ppid = pidtuple
348 delay = min(30,delay*1.2)
350 raise RuntimeError, "Failed to set up build slave %s: cannot get pid" % (self.home_path,)
352 self._logger.info("Deploying %s", self)
354 def _do_wait_build(self):
355 pid = self._build_pid
356 ppid = self._build_ppid
362 status = rspawn.remote_status(
364 host = self.node.hostname,
366 user = self.node.slicename,
368 ident_key = self.node.ident_path,
369 server_key = self.node.server_key
372 if status is not rspawn.RUNNING:
373 self._build_pid = self._build_ppid = None
377 self._logger.info("Waiting for %s to finish building %s", self,
378 "(build slave)" if self._master is not None else "(build master)")
381 time.sleep(delay*(0.5+random.random()))
382 delay = min(30,delay*1.2)
385 (out, err), proc = self._popen_ssh_command(
386 "cat %(token_path)s" % {
387 'token_path' : os.path.join(self.home_path, 'build.token'),
391 if not proc.wait() and out:
392 slave_token = out.strip()
394 if slave_token != self._master_token:
395 # Get buildlog for the error message
397 (buildlog, err), proc = self._popen_ssh_command(
398 "cat %(buildlog)s" % {
399 'buildlog' : os.path.join(self.home_path, 'buildlog'),
400 'buildscript' : os.path.join(self.home_path, 'nepi-build.sh'),
406 raise RuntimeError, "Failed to set up application %s: "\
407 "build failed, got wrong token from pid %s/%s "\
408 "(expected %r, got %r), see buildlog: %s" % (
409 self.home_path, pid, ppid, self._master_token, slave_token, buildlog)
411 self._logger.info("Built %s", self)
413 def _do_kill_build(self):
414 pid = self._build_pid
415 ppid = self._build_ppid
418 self._logger.info("Killing build of %s", self)
421 host = self.node.hostname,
423 user = self.node.slicename,
425 ident_key = self.node.ident_path
429 def _do_build_master(self):
430 if not self.sources and not self.build and not self.buildDepends:
434 sources = self.sources.split(' ')
440 "%s@%s:%s" % (self.node.slicename, self.node.hostname,
441 os.path.join(self.home_path,'.'),)
443 except RuntimeError, e:
444 raise RuntimeError, "Failed upload source file %r: %s %s" \
445 % (sources, e.args[0], e.args[1],)
447 buildscript = cStringIO.StringIO()
449 if self.buildDepends:
450 # Install build dependencies
452 "sudo -S yum -y install %(packages)s\n" % {
453 'packages' : self.buildDepends
461 "mkdir -p build && ( cd build && ( %(command)s ) )\n" % {
462 'command' : self._replace_paths(self.build),
463 'home' : server.shell_escape(self.home_path),
468 buildscript.write("tar czf build.tar.gz build\n")
471 buildscript.write("echo %(master_token)s > build.token" % {
472 'master_token' : server.shell_escape(self._master_token)
479 def _do_install(self):
481 self._logger.info("Installing %s", self)
483 # Install application
485 self._popen_ssh_command(
486 "cd %(home)s && cd build && ( %(command)s ) > ${HOME}/%(home)s/installlog 2>&1 || ( tail ${HOME}/%(home)s/{install,build}log >&2 && false )" % \
488 'command' : self._replace_paths(self.install),
489 'home' : server.shell_escape(self.home_path),
492 except RuntimeError, e:
493 raise RuntimeError, "Failed install build sources: %s %s" % (e.args[0], e.args[1],)
495 def set_master(self, master):
496 self._master = master
498 def install_keys(self, prk, puk, passphrase):
500 self._master_passphrase = passphrase
501 self._master_prk = prk
502 self._master_puk = puk
503 self._master_prk_name = os.path.basename(prk.name)
504 self._master_puk_name = os.path.basename(puk.name)
506 def _do_install_keys(self):
507 prk = self._master_prk
508 puk = self._master_puk
512 [ prk.name, puk.name ],
513 '%s@%s:%s' % (self.node.slicename, self.node.hostname, self.home_path )
515 except RuntimeError, e:
516 raise RuntimeError, "Failed to set up application deployment keys: %s %s" \
517 % (e.args[0], e.args[1],)
521 cStringIO.StringIO('%s,%s %s\n' % (
522 self._master.node.hostname, socket.gethostbyname(self._master.node.hostname),
523 self._master.node.server_key)),
524 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
525 os.path.join(self.home_path,"master_known_hosts") )
527 except RuntimeError, e:
528 raise RuntimeError, "Failed to set up application deployment keys: %s %s" \
529 % (e.args[0], e.args[1],)
532 self._master_prk = None
533 self._master_puk = None
536 # make sure there's no leftover build processes
537 self._do_kill_build()
540 def _popen_scp(self, src, dst, retry = True):
541 (out,err),proc = server.popen_scp(
546 ident_key = self.node.ident_path,
547 server_key = self.node.server_key
550 if server.eintr_retry(proc.wait)():
551 raise RuntimeError, (out, err)
552 return (out, err), proc
556 def _popen_ssh_command(self, command, retry = True, noerrors=False):
557 (out,err),proc = server.popen_ssh_command(
559 host = self.node.hostname,
561 user = self.node.slicename,
563 ident_key = self.node.ident_path,
564 server_key = self.node.server_key
567 if server.eintr_retry(proc.wait)():
569 raise RuntimeError, (out, err)
570 return (out, err), proc
572 class Application(Dependency):
574 An application also has dependencies, but also a command to be ran and monitored.
576 It adds the output of that command as traces.
579 TRACES = ('stdout','stderr','buildlog')
581 def __init__(self, api=None):
582 super(Application,self).__init__(api)
592 # Those are filled when the app is started
593 # Having both pid and ppid makes it harder
594 # for pid rollover to induce tracking mistakes
595 self._started = False
599 # Do not add to the python path of nodes
600 self.add_to_path = False
603 return "%s<command:%s%s>" % (
604 self.__class__.__name__,
605 "sudo " if self.sudo else "",
610 self._logger.info("Starting %s", self)
612 # Create shell script with the command
613 # This way, complex commands and scripts can be ran seamlessly
615 command = cStringIO.StringIO()
616 command.write('export PYTHONPATH=$PYTHONPATH:%s\n' % (
617 ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.node.pythonpath])
619 command.write('export PATH=$PATH:%s\n' % (
620 ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.node.pythonpath])
623 for envkey, envvals in self.node.env.iteritems():
624 for envval in envvals:
625 command.write('export %s=%s\n' % (envkey, envval))
626 command.write(self.command)
632 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
633 os.path.join(self.home_path, "app.sh"))
635 except RuntimeError, e:
636 raise RuntimeError, "Failed to set up application: %s %s" \
637 % (e.args[0], e.args[1],)
639 # Start process in a "daemonized" way, using nohup and heavy
640 # stdin/out redirection to avoid connection issues
641 (out,err),proc = rspawn.remote_spawn(
642 self._replace_paths("bash ./app.sh"),
645 home = self.home_path,
646 stdin = 'stdin' if self.stdin is not None else '/dev/null',
647 stdout = 'stdout' if self.stdout else '/dev/null',
648 stderr = 'stderr' if self.stderr else '/dev/null',
651 host = self.node.hostname,
653 user = self.node.slicename,
655 ident_key = self.node.ident_path,
656 server_key = self.node.server_key
660 raise RuntimeError, "Failed to set up application: %s %s" % (out,err,)
666 # NOTE: wait a bit for the pidfile to be created
667 if self._started and not self._pid or not self._ppid:
668 pidtuple = rspawn.remote_check_pid(
669 os.path.join(self.home_path,'pid'),
670 host = self.node.hostname,
672 user = self.node.slicename,
674 ident_key = self.node.ident_path,
675 server_key = self.node.server_key
679 self._pid, self._ppid = pidtuple
683 if not self._started:
684 return AS.STATUS_NOT_STARTED
685 elif not self._pid or not self._ppid:
686 return AS.STATUS_NOT_STARTED
688 status = rspawn.remote_status(
689 self._pid, self._ppid,
690 host = self.node.hostname,
692 user = self.node.slicename,
694 ident_key = self.node.ident_path,
695 server_key = self.node.server_key
698 if status is rspawn.NOT_STARTED:
699 return AS.STATUS_NOT_STARTED
700 elif status is rspawn.RUNNING:
701 return AS.STATUS_RUNNING
702 elif status is rspawn.FINISHED:
703 return AS.STATUS_FINISHED
706 return AS.STATUS_NOT_STARTED
709 status = self.status()
710 if status == AS.STATUS_RUNNING:
711 # kill by ppid+pid - SIGTERM first, then try SIGKILL
713 self._pid, self._ppid,
714 host = self.node.hostname,
716 user = self.node.slicename,
718 ident_key = self.node.ident_path,
719 server_key = self.node.server_key
721 self._logger.info("Killed %s", self)
724 class NepiDependency(Dependency):
726 This dependency adds nepi itself to the python path,
727 so that you may run testbeds within PL nodes.
730 # Class attribute holding a *weak* reference to the shared NEPI tar file
731 # so that they may share it. Don't operate on the file itself, it would
732 # be a mess, just use its path.
733 _shared_nepi_tar = None
735 def __init__(self, api = None):
736 super(NepiDependency, self).__init__(api)
740 self.depends = 'python python-ipaddr python-setuptools'
742 # our sources are in our ad-hoc tarball
743 self.sources = self.tarball.name
745 tarname = os.path.basename(self.tarball.name)
747 # it's already built - just move the tarball into place
748 self.build = "mv -f ${SOURCES}/%s ." % (tarname,)
750 # unpack it into sources, and we're done
751 self.install = "tar xzf ${BUILD}/%s -C .." % (tarname,)
755 if self._tarball is None:
756 shared_tar = self._shared_nepi_tar and self._shared_nepi_tar()
757 if shared_tar is not None:
758 self._tarball = shared_tar
760 # Build an ad-hoc tarball
765 shared_tar = tempfile.NamedTemporaryFile(prefix='nepi-src-', suffix='.tar.gz')
767 proc = subprocess.Popen(
768 ["tar", "czf", shared_tar.name,
769 '-C', os.path.join(os.path.dirname(os.path.dirname(nepi.__file__)),'.'),
771 stdout = open("/dev/null","w"),
772 stdin = open("/dev/null","r"))
775 raise RuntimeError, "Failed to create nepi tarball"
777 self._tarball = self._shared_nepi_tar = shared_tar
781 class NS3Dependency(Dependency):
783 This dependency adds NS3 libraries to the library paths,
784 so that you may run the NS3 testbed within PL nodes.
786 You'll also need the NepiDependency.
789 def __init__(self, api = None):
790 super(NS3Dependency, self).__init__(api)
792 self.buildDepends = 'make waf gcc gcc-c++ gccxml unzip'
794 # We have to download the sources, untar, build...
795 pybindgen_source_url = "http://pybindgen.googlecode.com/files/pybindgen-0.15.0.zip"
796 pygccxml_source_url = "http://leaseweb.dl.sourceforge.net/project/pygccxml/pygccxml/pygccxml-1.0/pygccxml-1.0.0.zip"
797 ns3_source_url = "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/ns-3.9-nepi/archive/tip.tar.gz"
798 passfd_source_url = "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/python-passfd/archive/tip.tar.gz"
802 " python -c 'import pygccxml, pybindgen, passfd' && "
803 " test -f lib/_ns3.so && "
804 " test -f lib/libns3.so "
806 # Not working, rebuild
807 # Archive SHA1 sums to check
808 "echo '7158877faff2254e6c094bf18e6b4283cac19137 pygccxml-1.0.0.zip' > archive_sums.txt && "
809 "echo 'ddc7c5d288e1bacb1307114878956762c5146fac pybindgen-src.zip' >> archive_sums.txt && "
810 " ( " # check existing files
811 " sha1sum -c archive_sums.txt && "
812 " test -f passfd-src.tar.gz && "
813 " test -f ns3-src.tar.gz "
814 " ) || ( " # nope? re-download
815 " rm -f pybindgen-src.zip pygccxml-1.0.0.zip passfd-src.tar.gz ns3-src.tar.gz && "
816 " wget -q -c -O pybindgen-src.zip %(pybindgen_source_url)s && " # continue, to exploit the case when it has already been dl'ed
817 " wget -q -c -O pygccxml-1.0.0.zip %(pygccxml_source_url)s && "
818 " wget -q -c -O passfd-src.tar.gz %(passfd_source_url)s && "
819 " wget -q -c -O ns3-src.tar.gz %(ns3_source_url)s && "
820 " sha1sum -c archive_sums.txt " # Check SHA1 sums when applicable
822 "unzip -n pybindgen-src.zip && " # Do not overwrite files, to exploit the case when it has already been built
823 "unzip -n pygccxml-1.0.0.zip && "
824 "mkdir -p ns3-src && "
825 "mkdir -p passfd-src && "
826 "tar xzf ns3-src.tar.gz --strip-components=1 -C ns3-src && "
827 "tar xzf passfd-src.tar.gz --strip-components=1 -C passfd-src && "
828 "rm -rf target && " # mv doesn't like unclean targets
829 "mkdir -p target && "
830 "cd pygccxml-1.0.0 && "
831 "rm -rf unittests docs && " # pygccxml has ~100M of unit tests - excessive - docs aren't needed either
832 "python setup.py build && "
833 "python setup.py install --install-lib ${BUILD}/target && "
834 "python setup.py clean && "
835 "cd ../pybindgen-0.15.0 && "
836 "export PYTHONPATH=$PYTHONPATH:${BUILD}/target && "
837 "./waf configure --prefix=${BUILD}/target -d release && "
841 "mv -f ${BUILD}/target/lib/python*/site-packages/pybindgen ${BUILD}/target/. && "
842 "rm -rf ${BUILD}/target/lib && "
843 "cd ../passfd-src && "
844 "python setup.py build && "
845 "python setup.py install --install-lib ${BUILD}/target && "
846 "python setup.py clean && "
848 "./waf configure --prefix=${BUILD}/target -d release --disable-examples --high-precision-as-double && "
854 pybindgen_source_url = server.shell_escape(pybindgen_source_url),
855 pygccxml_source_url = server.shell_escape(pygccxml_source_url),
856 ns3_source_url = server.shell_escape(ns3_source_url),
857 passfd_source_url = server.shell_escape(passfd_source_url),
860 # Just move ${BUILD}/target
864 " python -c 'import pygccxml, pybindgen, passfd' && "
865 " test -f lib/_ns3.so && "
866 " test -f lib/libns3.so "
868 # Not working, reinstall
869 "test -d ${BUILD}/target && "
870 "[[ \"x\" != \"x$(find ${BUILD}/target -mindepth 1 -print -quit)\" ]] &&"
871 "( for i in ${BUILD}/target/* ; do rm -rf ${SOURCES}/${i##*/} ; done ) && " # mv doesn't like unclean targets
872 "mv -f ${BUILD}/target/* ${SOURCES}"
876 # Set extra environment paths
877 self.env['NEPI_NS3BINDINGS'] = "${SOURCES}/lib"
878 self.env['NEPI_NS3LIBRARY'] = "${SOURCES}/lib/libns3.so"
882 if self._tarball is None:
883 shared_tar = self._shared_nepi_tar and self._shared_nepi_tar()
884 if shared_tar is not None:
885 self._tarball = shared_tar
887 # Build an ad-hoc tarball
892 shared_tar = tempfile.NamedTemporaryFile(prefix='nepi-src-', suffix='.tar.gz')
894 proc = subprocess.Popen(
895 ["tar", "czf", shared_tar.name,
896 '-C', os.path.join(os.path.dirname(os.path.dirname(nepi.__file__)),'.'),
898 stdout = open("/dev/null","w"),
899 stdin = open("/dev/null","r"))
902 raise RuntimeError, "Failed to create nepi tarball"
904 self._tarball = self._shared_nepi_tar = shared_tar
908 class YumDependency(Dependency):
910 This dependency is an internal helper class used to
911 efficiently distribute yum-downloaded rpms.
913 It temporarily sets the yum cache as persistent in the
914 build master, and installs all the required packages.
916 The rpm packages left in the yum cache are gathered and
917 distributed by the underlying Dependency in an efficient
918 manner. Build slaves will then install those rpms back in
919 the cache before issuing the install command.
921 When packages have been installed already, nothing but an
922 empty tar is distributed.
925 # Class attribute holding a *weak* reference to the shared NEPI tar file
926 # so that they may share it. Don't operate on the file itself, it would
927 # be a mess, just use its path.
928 _shared_nepi_tar = None
930 def _build_get(self):
931 # canonical representation of dependencies
932 depends = ' '.join( sorted( (self.depends or "").split(' ') ) )
934 # download rpms and pack into a tar archive
936 "sudo -S sed -i -r 's/keepcache *= *0/keepcache=1/' /etc/yum.conf && "
938 "sudo -S yum -y install %s ; "
939 "tar -C /var/cache/yum -rf ${BUILD}/packages.tar $(find /var/cache/yum -iname '*.rpm')"
940 " ) || /bin/true ) && "
941 "sudo -S sed -i -r 's/keepcache *= *1/keepcache=0/' /etc/yum.conf && "
942 "sudo -S yum -y clean packages "
944 def _build_set(self, value):
947 build = property(_build_get, _build_set)
949 def _install_get(self):
950 # canonical representation of dependencies
951 depends = ' '.join( sorted( (self.depends or "").split(' ') ) )
953 # unpack cached rpms into yum cache, install, and cleanup
955 "tar -k --keep-newer-files -C /var/cache/yum xzf packages.tar && "
956 "yum -y install %s && "
957 "yum -y clean packages "
959 def _install_set(self, value):
962 isntall = property(_install_get, _install_set)