2 # -*- coding: utf-8 -*-
4 from constants import TESTBED_ID
10 import nepi.util.server as server
21 from nepi.util.constants import ApplicationStatus as AS
23 class Dependency(object):
25 A Dependency is in every respect like an application.
27 It depends on some packages, it may require building binaries, it must deploy
30 But it has no command. Dependencies aren't ever started, or stopped, and have
36 def __init__(self, api=None):
48 self.buildDepends = None
50 self.rpmFusion = False
58 self.add_to_path = True
60 # Those are filled when the app is configured
63 # Those are filled when an actual node is connected
66 # Those are filled when the app is started
67 # Having both pid and ppid makes it harder
68 # for pid rollover to induce tracking mistakes
75 # Spanning tree deployment
77 self._master_passphrase = None
78 self._master_prk = None
79 self._master_puk = None
80 self._master_token = os.urandom(8).encode("hex")
81 self._build_pid = None
82 self._build_ppid = None
85 self._logger = logging.getLogger('nepi.testbeds.planetlab')
90 self.__class__.__name__,
91 ' '.join(filter(bool,(self.depends, self.sources)))
95 if self.home_path is None:
96 raise AssertionError, "Misconfigured application: missing home path"
97 if self.node.ident_path is None or not os.access(self.node.ident_path, os.R_OK):
98 raise AssertionError, "Misconfigured application: missing slice SSH key"
100 raise AssertionError, "Misconfigured application: unconnected node"
101 if self.node.hostname is None:
102 raise AssertionError, "Misconfigured application: misconfigured node"
103 if self.node.slicename is None:
104 raise AssertionError, "Misconfigured application: unspecified slice"
106 def check_bad_host(self, out, err):
108 Called whenever an operation fails, it's given the output to be checked for
109 telltale signs of unhealthy hosts.
113 def remote_trace_path(self, whichtrace):
114 if whichtrace in self.TRACES:
115 tracefile = os.path.join(self.home_path, whichtrace)
121 def remote_trace_name(self, whichtrace):
122 if whichtrace in self.TRACES:
126 def sync_trace(self, local_dir, whichtrace):
127 tracefile = self.remote_trace_path(whichtrace)
131 local_path = os.path.join(local_dir, tracefile)
133 # create parent local folders
134 proc = subprocess.Popen(
135 ["mkdir", "-p", os.path.dirname(local_path)],
136 stdout = open("/dev/null","w"),
137 stdin = open("/dev/null","r"))
140 raise RuntimeError, "Failed to synchronize trace"
145 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
149 except RuntimeError, e:
150 raise RuntimeError, "Failed to synchronize trace: %s %s" \
151 % (e.args[0], e.args[1],)
156 # We assume a correct deployment, so recovery only
157 # means we mark this dependency as deployed
161 self._logger.info("Setting up %s", self)
167 def async_setup(self):
168 if not self._setuper:
173 self._setuper._exc.append(sys.exc_info())
174 self._setuper = threading.Thread(
176 self._setuper._exc = []
177 self._setuper.start()
179 def async_setup_wait(self):
181 self._logger.info("Waiting for %s to be setup", self)
185 if self._setuper._exc:
186 exctyp,exval,exctrace = self._setuper._exc[0]
187 raise exctyp,exval,exctrace
189 raise RuntimeError, "Failed to setup application"
191 self._logger.info("Setup ready: %s at %s", self, self.node.hostname)
195 def _make_home(self):
196 # Make sure all the paths are created where
197 # they have to be created for deployment
200 self._popen_ssh_command(
201 "mkdir -p %(home)s && ( rm -f %(home)s/{pid,build-pid,nepi-build.sh} >/dev/null 2>&1 || /bin/true )" \
202 % { 'home' : server.shell_escape(self.home_path) },
206 except RuntimeError, e:
207 raise RuntimeError, "Failed to set up application %s: %s %s" % (self.home_path, e.args[0], e.args[1],)
210 # Write program input
213 cStringIO.StringIO(self.stdin),
214 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
215 os.path.join(self.home_path, 'stdin') ),
217 except RuntimeError, e:
218 raise RuntimeError, "Failed to set up application %s: %s %s" \
219 % (self.home_path, e.args[0], e.args[1],)
221 def _replace_paths(self, command):
223 Replace all special path tags with shell-escaped actual paths.
225 # need to append ${HOME} if paths aren't absolute, to MAKE them absolute.
226 root = '' if self.home_path.startswith('/') else "${HOME}/"
228 .replace("${SOURCES}", root+server.shell_escape(self.home_path))
229 .replace("${BUILD}", root+server.shell_escape(os.path.join(self.home_path,'build'))) )
231 def _launch_build(self):
232 if self._master is not None:
233 self._do_install_keys()
234 buildscript = self._do_build_slave()
236 buildscript = self._do_build_master()
238 if buildscript is not None:
239 self._logger.info("Building %s at %s", self, self.node.hostname)
241 # upload build script
245 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
246 os.path.join(self.home_path, 'nepi-build.sh') )
248 except RuntimeError, e:
249 raise RuntimeError, "Failed to set up application %s: %s %s" \
250 % (self.home_path, e.args[0], e.args[1],)
253 self._do_launch_build()
255 def _finish_build(self):
256 self._do_wait_build()
259 def _do_build_slave(self):
260 if not self.sources and not self.build:
263 # Create build script
267 sources = self.sources.split(' ')
269 "%s@%s:%s" % (self._master.node.slicename, self._master.node.hostname,
270 os.path.join(self._master.home_path, os.path.basename(source)),)
271 for source in sources
276 "%s@%s:%s" % (self._master.node.slicename, self._master.node.hostname,
277 os.path.join(self._master.home_path, 'build.tar.gz'),)
280 sshopts = "-o ConnectTimeout=30 -o ConnectionAttempts=3 -o ServerAliveInterval=30 -o TCPKeepAlive=yes"
282 launch_agent = "{ ( echo -e '#!/bin/sh\\ncat' > .ssh-askpass ) && chmod u+x .ssh-askpass"\
283 " && export SSH_ASKPASS=$(pwd)/.ssh-askpass "\
284 " && ssh-agent > .ssh-agent.sh ; } && . ./.ssh-agent.sh && ( echo $NEPI_MASTER_PASSPHRASE | ssh-add %(prk)s ) && rm -rf %(prk)s %(puk)s" % \
286 'prk' : server.shell_escape(self._master_prk_name),
287 'puk' : server.shell_escape(self._master_puk_name),
290 kill_agent = "kill $SSH_AGENT_PID"
293 "{ . ./.ssh-agent.sh ; "
294 "while [[ $(. ./.ssh-agent.sh > /dev/null ; ssh -q -o UserKnownHostsFile=%(hostkey)s %(sshopts)s %(master)s cat %(token_path)s.retcode || /bin/true) != %(token)s ]] ; do sleep 5 ; done ; "
295 "if [[ $(. ./.ssh-agent.sh > /dev/null ; ssh -q -o UserKnownHostsFile=%(hostkey)s %(sshopts)s %(master)s cat %(token_path)s || /bin/true) != %(token)s ]] ; then echo BAD TOKEN ; exit 1 ; fi ; "
298 'hostkey' : 'master_known_hosts',
299 'master' : "%s@%s" % (self._master.node.slicename, self._master.node.hostname),
300 'token_path' : os.path.join(self._master.home_path, 'build.token'),
301 'token' : server.shell_escape(self._master._master_token),
305 syncfiles = ". ./.ssh-agent.sh && scp -p -o UserKnownHostsFile=%(hostkey)s %(sshopts)s %(files)s ." % {
306 'hostkey' : 'master_known_hosts',
307 'files' : ' '.join(files),
311 syncfiles += " && tar xzf build.tar.gz"
312 syncfiles += " && ( echo %s > build.token )" % (server.shell_escape(self._master_token),)
313 syncfiles += " && ( echo %s > build.token.retcode )" % (server.shell_escape(self._master_token),)
314 syncfiles = "{ . ./.ssh-agent.sh ; %s ; }" % (syncfiles,)
316 cleanup = "{ . ./.ssh-agent.sh ; kill $SSH_AGENT_PID ; rm -rf %(prk)s %(puk)s master_known_hosts .ssh-askpass ; }" % {
317 'prk' : server.shell_escape(self._master_prk_name),
318 'puk' : server.shell_escape(self._master_puk_name),
321 slavescript = "( ( %(launch_agent)s && %(waitmaster)s && %(syncfiles)s && %(kill_agent)s && %(cleanup)s ) || %(cleanup)s ) ; echo %(token)s > build.token.retcode" % {
322 'waitmaster' : waitmaster,
323 'syncfiles' : syncfiles,
325 'kill_agent' : kill_agent,
326 'launch_agent' : launch_agent,
327 'home' : server.shell_escape(self.home_path),
328 'token' : server.shell_escape(self._master_token),
331 return cStringIO.StringIO(slavescript)
333 def _do_launch_build(self):
334 script = "bash ./nepi-build.sh"
335 if self._master_passphrase:
336 script = "NEPI_MASTER_PASSPHRASE=%s %s" % (
337 server.shell_escape(self._master_passphrase),
340 (out,err),proc = rspawn.remote_spawn(
342 pidfile = 'build-pid',
343 home = self.home_path,
346 stderr = rspawn.STDOUT,
348 host = self.node.hostname,
350 user = self.node.slicename,
352 ident_key = self.node.ident_path,
353 server_key = self.node.server_key
357 if self.check_bad_host(out, err):
358 self.node.blacklist()
359 raise RuntimeError, "Failed to set up build slave %s: %s %s" % (self.home_path, out,err,)
365 pidtuple = rspawn.remote_check_pid(
366 os.path.join(self.home_path,'build-pid'),
367 host = self.node.hostname,
369 user = self.node.slicename,
371 ident_key = self.node.ident_path,
372 server_key = self.node.server_key
377 self._build_pid, self._build_ppid = pidtuple
381 delay = min(30,delay*1.2)
383 raise RuntimeError, "Failed to set up build slave %s: cannot get pid" % (self.home_path,)
385 self._logger.info("Deploying %s at %s", self, self.node.hostname)
387 def _do_wait_build(self):
388 pid = self._build_pid
389 ppid = self._build_ppid
396 status = rspawn.remote_status(
398 host = self.node.hostname,
400 user = self.node.slicename,
402 ident_key = self.node.ident_path,
403 server_key = self.node.server_key
406 if status is rspawn.FINISHED:
407 self._build_pid = self._build_ppid = None
409 elif status is not rspawn.RUNNING:
411 time.sleep(delay*(5.5+random.random()))
413 self._build_pid = self._build_ppid = None
417 self._logger.info("Waiting for %s to finish building at %s %s", self, self.node.hostname,
418 "(build slave)" if self._master is not None else "(build master)")
421 time.sleep(delay*(0.5+random.random()))
422 delay = min(30,delay*1.2)
428 (out, err), proc = self._popen_ssh_command(
429 "cat %(token_path)s" % {
430 'token_path' : os.path.join(self.home_path, 'build.token'),
434 if not proc.wait() and out:
435 slave_token = out.strip()
442 if slave_token != self._master_token:
443 # Get buildlog for the error message
445 (buildlog, err), proc = self._popen_ssh_command(
446 "cat %(buildlog)s" % {
447 'buildlog' : os.path.join(self.home_path, 'buildlog'),
448 'buildscript' : os.path.join(self.home_path, 'nepi-build.sh'),
455 if self.check_bad_host(buildlog, err):
456 self.node.blacklist()
458 raise RuntimeError, "Failed to set up application %s: "\
459 "build failed, got wrong token from pid %s/%s "\
460 "(expected %r, got %r), see buildlog at %s:\n%s" % (
461 self.home_path, pid, ppid, self._master_token, slave_token, self.node.hostname, buildlog)
463 self._logger.info("Built %s at %s", self, self.node.hostname)
465 def _do_kill_build(self):
466 pid = self._build_pid
467 ppid = self._build_ppid
470 self._logger.info("Killing build of %s", self)
473 host = self.node.hostname,
475 user = self.node.slicename,
477 ident_key = self.node.ident_path
481 def _do_build_master(self):
482 if not self.sources and not self.build and not self.buildDepends:
486 sources = self.sources.split(' ')
492 "%s@%s:%s" % (self.node.slicename, self.node.hostname,
493 os.path.join(self.home_path,'.'),)
495 except RuntimeError, e:
496 raise RuntimeError, "Failed upload source file %r: %s %s" \
497 % (sources, e.args[0], e.args[1],)
499 buildscript = cStringIO.StringIO()
501 buildscript.write("(\n")
503 if self.buildDepends:
504 # Install build dependencies
506 "sudo -S yum -y install %(packages)s\n" % {
507 'packages' : self.buildDepends
515 "mkdir -p build && ( cd build && ( %(command)s ) )\n" % {
516 'command' : self._replace_paths(self.build),
517 'home' : server.shell_escape(self.home_path),
522 buildscript.write("tar czf build.tar.gz build\n")
525 buildscript.write("echo %(master_token)s > build.token ) ; echo %(master_token)s > build.token.retcode" % {
526 'master_token' : server.shell_escape(self._master_token)
533 def _do_install(self):
535 self._logger.info("Installing %s at %s", self, self.node.hostname)
537 # Install application
539 self._popen_ssh_command(
540 "cd %(home)s && cd build && ( %(command)s ) > ${HOME}/%(home)s/installlog 2>&1 || ( tail ${HOME}/%(home)s/{install,build}log >&2 && false )" % \
542 'command' : self._replace_paths(self.install),
543 'home' : server.shell_escape(self.home_path),
546 except RuntimeError, e:
547 if self.check_bad_host(e.args[0], e.args[1]):
548 self.node.blacklist()
549 raise RuntimeError, "Failed install build sources: %s %s" % (e.args[0], e.args[1],)
551 def set_master(self, master):
552 self._master = master
554 def install_keys(self, prk, puk, passphrase):
556 self._master_passphrase = passphrase
557 self._master_prk = prk
558 self._master_puk = puk
559 self._master_prk_name = os.path.basename(prk.name)
560 self._master_puk_name = os.path.basename(puk.name)
562 def _do_install_keys(self):
563 prk = self._master_prk
564 puk = self._master_puk
568 [ prk.name, puk.name ],
569 '%s@%s:%s' % (self.node.slicename, self.node.hostname, self.home_path )
571 except RuntimeError, e:
572 raise RuntimeError, "Failed to set up application deployment keys: %s %s" \
573 % (e.args[0], e.args[1],)
577 cStringIO.StringIO('%s,%s %s\n' % (
578 self._master.node.hostname, socket.gethostbyname(self._master.node.hostname),
579 self._master.node.server_key)),
580 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
581 os.path.join(self.home_path,"master_known_hosts") )
583 except RuntimeError, e:
584 raise RuntimeError, "Failed to set up application deployment keys: %s %s" \
585 % (e.args[0], e.args[1],)
588 self._master_prk = None
589 self._master_puk = None
592 # make sure there's no leftover build processes
593 self._do_kill_build()
596 def _popen_scp(self, src, dst, retry = 3):
599 (out,err),proc = server.popen_scp(
604 ident_key = self.node.ident_path,
605 server_key = self.node.server_key
608 if server.eintr_retry(proc.wait)():
609 raise RuntimeError, (out, err)
610 return (out, err), proc
619 def _popen_ssh_command(self, command, retry = 0, noerrors=False, timeout=None):
620 (out,err),proc = server.popen_ssh_command(
622 host = self.node.hostname,
624 user = self.node.slicename,
626 ident_key = self.node.ident_path,
627 server_key = self.node.server_key,
632 if server.eintr_retry(proc.wait)():
634 raise RuntimeError, (out, err)
635 return (out, err), proc
637 class Application(Dependency):
639 An application also has dependencies, but also a command to be ran and monitored.
641 It adds the output of that command as traces.
644 TRACES = ('stdout','stderr','buildlog', 'output')
646 def __init__(self, api=None):
647 super(Application,self).__init__(api)
658 # Those are filled when the app is started
659 # Having both pid and ppid makes it harder
660 # for pid rollover to induce tracking mistakes
661 self._started = False
665 # Do not add to the python path of nodes
666 self.add_to_path = False
669 return "%s<command:%s%s>" % (
670 self.__class__.__name__,
671 "sudo " if self.sudo else "",
676 self._logger.info("Starting %s", self)
678 # Create shell script with the command
679 # This way, complex commands and scripts can be ran seamlessly
681 command = cStringIO.StringIO()
682 command.write('export PYTHONPATH=$PYTHONPATH:%s\n' % (
683 ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.node.pythonpath])
685 command.write('export PATH=$PATH:%s\n' % (
686 ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.node.pythonpath])
689 for envkey, envvals in self.node.env.iteritems():
690 for envval in envvals:
691 command.write('export %s=%s\n' % (envkey, envval))
692 command.write(self.command)
698 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
699 os.path.join(self.home_path, "app.sh"))
701 except RuntimeError, e:
702 raise RuntimeError, "Failed to set up application: %s %s" \
703 % (e.args[0], e.args[1],)
705 # Start process in a "daemonized" way, using nohup and heavy
706 # stdin/out redirection to avoid connection issues
707 (out,err),proc = rspawn.remote_spawn(
708 self._replace_paths("bash ./app.sh"),
711 home = self.home_path,
712 stdin = 'stdin' if self.stdin is not None else '/dev/null',
713 stdout = 'stdout' if self.stdout else '/dev/null',
714 stderr = 'stderr' if self.stderr else '/dev/null',
717 host = self.node.hostname,
719 user = self.node.slicename,
721 ident_key = self.node.ident_path,
722 server_key = self.node.server_key
726 if self.check_bad_host(out, err):
727 self.node.blacklist()
728 raise RuntimeError, "Failed to set up application: %s %s" % (out,err,)
733 # Assuming the application is running on PlanetLab,
734 # proper pidfiles should be present at the app's home path.
735 # So we mark this application as started, and check the pidfiles
741 # NOTE: wait a bit for the pidfile to be created
742 if self._started and not self._pid or not self._ppid:
743 pidtuple = rspawn.remote_check_pid(
744 os.path.join(self.home_path,'pid'),
745 host = self.node.hostname,
747 user = self.node.slicename,
749 ident_key = self.node.ident_path,
750 server_key = self.node.server_key
754 self._pid, self._ppid = pidtuple
758 if not self._started:
759 return AS.STATUS_NOT_STARTED
760 elif not self._pid or not self._ppid:
761 return AS.STATUS_NOT_STARTED
763 status = rspawn.remote_status(
764 self._pid, self._ppid,
765 host = self.node.hostname,
767 user = self.node.slicename,
769 ident_key = self.node.ident_path,
770 server_key = self.node.server_key
773 if status is rspawn.NOT_STARTED:
774 return AS.STATUS_NOT_STARTED
775 elif status is rspawn.RUNNING:
776 return AS.STATUS_RUNNING
777 elif status is rspawn.FINISHED:
778 return AS.STATUS_FINISHED
781 return AS.STATUS_NOT_STARTED
784 status = self.status()
785 if status == AS.STATUS_RUNNING:
786 # kill by ppid+pid - SIGTERM first, then try SIGKILL
788 self._pid, self._ppid,
789 host = self.node.hostname,
791 user = self.node.slicename,
793 ident_key = self.node.ident_path,
794 server_key = self.node.server_key,
797 self._logger.info("Killed %s", self)
800 class NepiDependency(Dependency):
802 This dependency adds nepi itself to the python path,
803 so that you may run testbeds within PL nodes.
806 # Class attribute holding a *weak* reference to the shared NEPI tar file
807 # so that they may share it. Don't operate on the file itself, it would
808 # be a mess, just use its path.
809 _shared_nepi_tar = None
811 def __init__(self, api = None):
812 super(NepiDependency, self).__init__(api)
816 self.depends = 'python python-ipaddr python-setuptools'
818 # our sources are in our ad-hoc tarball
819 self.sources = self.tarball.name
821 tarname = os.path.basename(self.tarball.name)
823 # it's already built - just move the tarball into place
824 self.build = "mv -f ${SOURCES}/%s ." % (tarname,)
826 # unpack it into sources, and we're done
827 self.install = "tar xzf ${BUILD}/%s -C .." % (tarname,)
831 if self._tarball is None:
832 shared_tar = self._shared_nepi_tar and self._shared_nepi_tar()
833 if shared_tar is not None:
834 self._tarball = shared_tar
836 # Build an ad-hoc tarball
841 shared_tar = tempfile.NamedTemporaryFile(prefix='nepi-src-', suffix='.tar.gz')
843 proc = subprocess.Popen(
844 ["tar", "czf", shared_tar.name,
845 '-C', os.path.join(os.path.dirname(os.path.dirname(nepi.__file__)),'.'),
847 stdout = open("/dev/null","w"),
848 stdin = open("/dev/null","r"))
851 raise RuntimeError, "Failed to create nepi tarball"
853 self._tarball = self._shared_nepi_tar = shared_tar
857 class NS3Dependency(Dependency):
859 This dependency adds NS3 libraries to the library paths,
860 so that you may run the NS3 testbed within PL nodes.
862 You'll also need the NepiDependency.
865 def __init__(self, api = None):
866 super(NS3Dependency, self).__init__(api)
868 self.buildDepends = 'make waf gcc gcc-c++ gccxml unzip'
870 # We have to download the sources, untar, build...
871 pybindgen_source_url = "http://yans.pl.sophia.inria.fr/trac/nepi/raw-attachment/wiki/WikiStart/pybindgen-r794.tar.gz"
872 pygccxml_source_url = "http://leaseweb.dl.sourceforge.net/project/pygccxml/pygccxml/pygccxml-1.0/pygccxml-1.0.0.zip"
873 ns3_source_url = "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/ns-3.11-nepi/archive/tip.tar.gz"
874 passfd_source_url = "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/python-passfd/archive/tip.tar.gz"
878 " python -c 'import pygccxml, pybindgen, passfd' && "
879 " test -f lib/ns/_core.so && "
880 " test -f lib/ns/__init__.py && "
881 " test -f lib/ns/core.py && "
882 " test -f lib/libns3-core.so && "
883 " LD_LIBRARY_PATH=lib PYTHONPATH=lib python -c 'import ns.core' "
885 # Not working, rebuild
886 # Archive SHA1 sums to check
887 "echo '7158877faff2254e6c094bf18e6b4283cac19137 pygccxml-1.0.0.zip' > archive_sums.txt && "
888 "echo 'a18c2ccffd0df517bc37e2f3a2475092517c43f2 pybindgen-src.tar.gz' >> archive_sums.txt && "
889 " ( " # check existing files
890 " sha1sum -c archive_sums.txt && "
891 " test -f passfd-src.tar.gz && "
892 " test -f ns3-src.tar.gz "
893 " ) || ( " # nope? re-download
894 " rm -f pybindgen-src.zip pygccxml-1.0.0.zip passfd-src.tar.gz ns3-src.tar.gz && "
895 " wget -q -c -O pybindgen-src.tar.gz %(pybindgen_source_url)s && " # continue, to exploit the case when it has already been dl'ed
896 " wget -q -c -O pygccxml-1.0.0.zip %(pygccxml_source_url)s && "
897 " wget -q -c -O passfd-src.tar.gz %(passfd_source_url)s && "
898 " wget -q -c -O ns3-src.tar.gz %(ns3_source_url)s && "
899 " sha1sum -c archive_sums.txt " # Check SHA1 sums when applicable
901 "unzip -n pygccxml-1.0.0.zip && "
902 "mkdir -p pybindgen-src && "
903 "mkdir -p ns3-src && "
904 "mkdir -p passfd-src && "
905 "tar xzf ns3-src.tar.gz --strip-components=1 -C ns3-src && "
906 "tar xzf passfd-src.tar.gz --strip-components=1 -C passfd-src && "
907 "tar xzf pybindgen-src.tar.gz --strip-components=1 -C pybindgen-src && "
908 "rm -rf target && " # mv doesn't like unclean targets
909 "mkdir -p target && "
910 "cd pygccxml-1.0.0 && "
911 "rm -rf unittests docs && " # pygccxml has ~100M of unit tests - excessive - docs aren't needed either
912 "python setup.py build && "
913 "python setup.py install --install-lib ${BUILD}/target && "
914 "python setup.py clean && "
915 "cd ../pybindgen-src && "
916 "export PYTHONPATH=$PYTHONPATH:${BUILD}/target && "
917 "./waf configure --prefix=${BUILD}/target -d release && "
921 "mv -f ${BUILD}/target/lib/python*/site-packages/pybindgen ${BUILD}/target/. && "
922 "rm -rf ${BUILD}/target/lib && "
923 "cd ../passfd-src && "
924 "python setup.py build && "
925 "python setup.py install --install-lib ${BUILD}/target && "
926 "python setup.py clean && "
928 "./waf configure --prefix=${BUILD}/target --with-pybindgen=../pybindgen-src -d release --disable-examples --disable-tests && "
931 "rm -f ${BUILD}/target/lib/*.so && "
932 "cp -a ${BUILD}/ns3-src/build/release/libns3*.so ${BUILD}/target/lib && "
933 "cp -a ${BUILD}/ns3-src/build/release/bindings/python/ns ${BUILD}/target/lib &&"
937 pybindgen_source_url = server.shell_escape(pybindgen_source_url),
938 pygccxml_source_url = server.shell_escape(pygccxml_source_url),
939 ns3_source_url = server.shell_escape(ns3_source_url),
940 passfd_source_url = server.shell_escape(passfd_source_url),
943 # Just move ${BUILD}/target
947 " python -c 'import pygccxml, pybindgen, passfd' && "
948 " test -f lib/ns/_core.so && "
949 " test -f lib/ns/__init__.py && "
950 " test -f lib/ns/core.py && "
951 " test -f lib/libns3-core.so && "
952 " LD_LIBRARY_PATH=lib PYTHONPATH=lib python -c 'import ns.core' "
954 # Not working, reinstall
955 "test -d ${BUILD}/target && "
956 "[[ \"x\" != \"x$(find ${BUILD}/target -mindepth 1 -print -quit)\" ]] &&"
957 "( for i in ${BUILD}/target/* ; do rm -rf ${SOURCES}/${i##*/} ; done ) && " # mv doesn't like unclean targets
958 "mv -f ${BUILD}/target/* ${SOURCES}"
962 # Set extra environment paths
963 self.env['NEPI_NS3BINDINGS'] = "${SOURCES}/lib"
964 self.env['NEPI_NS3LIBRARY'] = "${SOURCES}/lib"
968 if self._tarball is None:
969 shared_tar = self._shared_nepi_tar and self._shared_nepi_tar()
970 if shared_tar is not None:
971 self._tarball = shared_tar
973 # Build an ad-hoc tarball
978 shared_tar = tempfile.NamedTemporaryFile(prefix='nepi-src-', suffix='.tar.gz')
980 proc = subprocess.Popen(
981 ["tar", "czf", shared_tar.name,
982 '-C', os.path.join(os.path.dirname(os.path.dirname(nepi.__file__)),'.'),
984 stdout = open("/dev/null","w"),
985 stdin = open("/dev/null","r"))
988 raise RuntimeError, "Failed to create nepi tarball"
990 self._tarball = self._shared_nepi_tar = shared_tar
994 class YumDependency(Dependency):
996 This dependency is an internal helper class used to
997 efficiently distribute yum-downloaded rpms.
999 It temporarily sets the yum cache as persistent in the
1000 build master, and installs all the required packages.
1002 The rpm packages left in the yum cache are gathered and
1003 distributed by the underlying Dependency in an efficient
1004 manner. Build slaves will then install those rpms back in
1005 the cache before issuing the install command.
1007 When packages have been installed already, nothing but an
1008 empty tar is distributed.
1011 # Class attribute holding a *weak* reference to the shared NEPI tar file
1012 # so that they may share it. Don't operate on the file itself, it would
1013 # be a mess, just use its path.
1014 _shared_nepi_tar = None
1016 def _build_get(self):
1017 # canonical representation of dependencies
1018 depends = ' '.join( sorted( (self.depends or "").split(' ') ) )
1020 # download rpms and pack into a tar archive
1022 "sudo -S nice yum -y makecache && "
1023 "sudo -S sed -i -r 's/keepcache *= *0/keepcache=1/' /etc/yum.conf && "
1025 "sudo -S nice yum -y install %s ; "
1026 "rm -f ${BUILD}/packages.tar ; "
1027 "tar -C /var/cache/yum -rf ${BUILD}/packages.tar $(cd /var/cache/yum ; find -iname '*.rpm')"
1028 " ) || /bin/true ) && "
1029 "sudo -S sed -i -r 's/keepcache *= *1/keepcache=0/' /etc/yum.conf && "
1030 "( sudo -S nice yum -y clean packages || /bin/true ) "
1032 def _build_set(self, value):
1035 build = property(_build_get, _build_set)
1037 def _install_get(self):
1038 # canonical representation of dependencies
1039 depends = ' '.join( sorted( (self.depends or "").split(' ') ) )
1041 # unpack cached rpms into yum cache, install, and cleanup
1043 "sudo -S tar -k --keep-newer-files -C /var/cache/yum -xf packages.tar && "
1044 "sudo -S nice yum -y install %s && "
1045 "( sudo -S nice yum -y clean packages || /bin/true ) "
1047 def _install_set(self, value):
1050 install = property(_install_get, _install_set)
1052 def check_bad_host(self, out, err):
1053 badre = re.compile(r'(?:'
1054 r'The GPG keys listed for the ".*" repository are already installed but they are not correct for this package'
1055 r'|Error: Cannot retrieve repository metadata (repomd.xml) for repository: .*[.] Please verify its path and try again'
1056 r'|Error: disk I/O error'
1059 return badre.search(out) or badre.search(err)