1 # -*- coding: utf-8 -*-
3 from constants import TESTBED_ID
9 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 = os.urandom(8).encode("hex")
80 self._build_pid = None
81 self._build_ppid = None
84 self._logger = logging.getLogger('nepi.testbeds.planetlab')
89 self.__class__.__name__,
90 ' '.join(filter(bool,(self.depends, self.sources)))
94 if self.home_path is None:
95 raise AssertionError, "Misconfigured application: missing home path"
96 if self.node.ident_path is None or not os.access(self.node.ident_path, os.R_OK):
97 raise AssertionError, "Misconfigured application: missing slice SSH key"
99 raise AssertionError, "Misconfigured application: unconnected node"
100 if self.node.hostname is None:
101 raise AssertionError, "Misconfigured application: misconfigured node"
102 if self.node.slicename is None:
103 raise AssertionError, "Misconfigured application: unspecified slice"
105 def check_bad_host(self, out, err):
107 Called whenever an operation fails, it's given the output to be checked for
108 telltale signs of unhealthy hosts.
112 def remote_trace_path(self, whichtrace):
113 if whichtrace in self.TRACES:
114 tracefile = os.path.join(self.home_path, whichtrace)
120 def remote_trace_name(self, whichtrace):
121 if whichtrace in self.TRACES:
125 def sync_trace(self, local_dir, whichtrace):
126 tracefile = self.remote_trace_path(whichtrace)
130 local_path = os.path.join(local_dir, tracefile)
132 # create parent local folders
133 proc = subprocess.Popen(
134 ["mkdir", "-p", os.path.dirname(local_path)],
135 stdout = open("/dev/null","w"),
136 stdin = open("/dev/null","r"))
139 raise RuntimeError, "Failed to synchronize trace"
144 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
148 except RuntimeError, e:
149 raise RuntimeError, "Failed to synchronize trace: %s %s" \
150 % (e.args[0], e.args[1],)
155 # We assume a correct deployment, so recovery only
156 # means we mark this dependency as deployed
160 self._logger.info("Setting up %s", self)
166 def async_setup(self):
167 if not self._setuper:
172 self._setuper._exc.append(sys.exc_info())
173 self._setuper = threading.Thread(
175 self._setuper._exc = []
176 self._setuper.start()
178 def async_setup_wait(self):
180 self._logger.info("Waiting for %s to be setup", self)
184 if self._setuper._exc:
185 exctyp,exval,exctrace = self._setuper._exc[0]
186 raise exctyp,exval,exctrace
188 raise RuntimeError, "Failed to setup application"
190 self._logger.info("Setup ready: %s at %s", self, self.node.hostname)
194 def _make_home(self):
195 # Make sure all the paths are created where
196 # they have to be created for deployment
199 self._popen_ssh_command(
200 "mkdir -p %(home)s && ( rm -f %(home)s/{pid,build-pid,nepi-build.sh} >/dev/null 2>&1 || /bin/true )" \
201 % { 'home' : server.shell_escape(self.home_path) },
205 except RuntimeError, e:
206 raise RuntimeError, "Failed to set up application %s: %s %s" % (self.home_path, e.args[0], e.args[1],)
210 if not os.path.isfile(stdin):
211 stdin = cStringIO.StringIO(self.stdin)
213 # Write program input
215 self._popen_scp(stdin,
216 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
217 os.path.join(self.home_path, 'stdin') ),
219 except RuntimeError, e:
220 raise RuntimeError, "Failed to set up application %s: %s %s" \
221 % (self.home_path, e.args[0], e.args[1],)
223 def _replace_paths(self, command):
225 Replace all special path tags with shell-escaped actual paths.
227 # need to append ${HOME} if paths aren't absolute, to MAKE them absolute.
228 root = '' if self.home_path.startswith('/') else "${HOME}/"
230 .replace("${SOURCES}", root+server.shell_escape(self.home_path))
231 .replace("${BUILD}", root+server.shell_escape(os.path.join(self.home_path,'build'))) )
233 def _launch_build(self, trial=0):
234 if self._master is not None:
235 if not trial or self._master_prk is not None:
236 self._do_install_keys()
237 buildscript = self._do_build_slave()
239 buildscript = self._do_build_master()
241 if buildscript is not None:
242 self._logger.info("Building %s at %s", self, self.node.hostname)
244 # upload build script
248 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
249 os.path.join(self.home_path, 'nepi-build.sh') )
251 except RuntimeError, e:
252 raise RuntimeError, "Failed to set up application %s: %s %s" \
253 % (self.home_path, e.args[0], e.args[1],)
256 self._do_launch_build()
258 def _finish_build(self):
259 self._do_wait_build()
262 def _do_build_slave(self):
263 if not self.sources and not self.build:
266 # Create build script
270 sources = self.sources.split(' ')
272 "%s@%s:%s" % (self._master.node.slicename, self._master.node.hostip,
273 os.path.join(self._master.home_path, os.path.basename(source)),)
274 for source in sources
279 "%s@%s:%s" % (self._master.node.slicename, self._master.node.hostip,
280 os.path.join(self._master.home_path, 'build.tar.gz'),)
283 sshopts = "-o ConnectTimeout=30 -o ConnectionAttempts=3 -o ServerAliveInterval=30 -o TCPKeepAlive=yes"
285 launch_agent = "{ ( echo -e '#!/bin/sh\\ncat' > .ssh-askpass ) && chmod u+x .ssh-askpass"\
286 " && export SSH_ASKPASS=$(pwd)/.ssh-askpass "\
287 " && ssh-agent > .ssh-agent.sh ; } && . ./.ssh-agent.sh && ( echo $NEPI_MASTER_PASSPHRASE | ssh-add %(prk)s ) && rm -rf %(prk)s %(puk)s" % \
289 'prk' : server.shell_escape(self._master_prk_name),
290 'puk' : server.shell_escape(self._master_puk_name),
293 kill_agent = "kill $SSH_AGENT_PID"
297 "echo 'Checking master reachability' ; "
298 "if ping -c 3 %(master_host)s && (. ./.ssh-agent.sh > /dev/null ; ssh -o UserKnownHostsFile=%(hostkey)s %(sshopts)s %(master)s echo MASTER SAYS HI ) ; then "
299 "echo 'Master node reachable' ; "
301 "echo 'MASTER NODE UNREACHABLE' && "
304 ". ./.ssh-agent.sh ; "
305 "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 ; "
306 "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 ; "
309 'hostkey' : 'master_known_hosts',
310 'master' : "%s@%s" % (self._master.node.slicename, self._master.node.hostip),
311 'master_host' : self._master.node.hostip,
312 'token_path' : os.path.join(self._master.home_path, 'build.token'),
313 'token' : server.shell_escape(self._master._master_token),
317 syncfiles = ". ./.ssh-agent.sh && scp -p -o UserKnownHostsFile=%(hostkey)s %(sshopts)s %(files)s ." % {
318 'hostkey' : 'master_known_hosts',
319 'files' : ' '.join(files),
323 syncfiles += " && tar xzf build.tar.gz"
324 syncfiles += " && ( echo %s > build.token )" % (server.shell_escape(self._master_token),)
325 syncfiles += " && ( echo %s > build.token.retcode )" % (server.shell_escape(self._master_token),)
326 syncfiles = "{ . ./.ssh-agent.sh ; %s ; }" % (syncfiles,)
328 cleanup = "{ . ./.ssh-agent.sh ; kill $SSH_AGENT_PID ; rm -rf %(prk)s %(puk)s master_known_hosts .ssh-askpass ; }" % {
329 'prk' : server.shell_escape(self._master_prk_name),
330 'puk' : server.shell_escape(self._master_puk_name),
333 slavescript = "( ( %(launch_agent)s && %(waitmaster)s && %(syncfiles)s && %(kill_agent)s && %(cleanup)s ) || %(cleanup)s ) ; echo %(token)s > build.token.retcode" % {
334 'waitmaster' : waitmaster,
335 'syncfiles' : syncfiles,
337 'kill_agent' : kill_agent,
338 'launch_agent' : launch_agent,
339 'home' : server.shell_escape(self.home_path),
340 'token' : server.shell_escape(self._master_token),
343 return cStringIO.StringIO(slavescript)
345 def _do_launch_build(self):
346 script = "bash ./nepi-build.sh"
347 if self._master_passphrase:
348 script = "NEPI_MASTER_PASSPHRASE=%s %s" % (
349 server.shell_escape(self._master_passphrase),
352 (out,err),proc = rspawn.remote_spawn(
354 pidfile = 'build-pid',
355 home = self.home_path,
358 stderr = rspawn.STDOUT,
360 host = self.node.hostname,
362 user = self.node.slicename,
364 ident_key = self.node.ident_path,
365 server_key = self.node.server_key,
366 hostip = self.node.hostip,
370 if self.check_bad_host(out, err):
371 self.node.blacklist()
372 raise RuntimeError, "Failed to set up build slave %s: %s %s" % (self.home_path, out,err,)
378 pidtuple = rspawn.remote_check_pid(
379 os.path.join(self.home_path,'build-pid'),
380 host = self.node.hostname,
382 user = self.node.slicename,
384 ident_key = self.node.ident_path,
385 server_key = self.node.server_key,
386 hostip = self.node.hostip
391 self._build_pid, self._build_ppid = pidtuple
395 delay = min(30,delay*1.2)
397 raise RuntimeError, "Failed to set up build slave %s: cannot get pid" % (self.home_path,)
399 self._logger.info("Deploying %s at %s", self, self.node.hostname)
401 def _do_wait_build(self, trial=0):
402 pid = self._build_pid
403 ppid = self._build_ppid
410 status = rspawn.remote_status(
412 host = self.node.hostname,
414 user = self.node.slicename,
416 ident_key = self.node.ident_path,
417 server_key = self.node.server_key,
418 hostip = self.node.hostip
421 if status is rspawn.FINISHED:
422 self._build_pid = self._build_ppid = None
424 elif status is not rspawn.RUNNING:
425 self._logger.warn("Busted waiting for %s to finish building at %s %s", self, self.node.hostname,
426 "(build slave)" if self._master is not None else "(build master)")
428 time.sleep(delay*(5.5+random.random()))
430 self._build_pid = self._build_ppid = None
434 self._logger.info("Waiting for %s to finish building at %s %s", self, self.node.hostname,
435 "(build slave)" if self._master is not None else "(build master)")
438 time.sleep(delay*(0.5+random.random()))
439 delay = min(30,delay*1.2)
445 (out, err), proc = self._popen_ssh_command(
446 "cat %(token_path)s" % {
447 'token_path' : os.path.join(self.home_path, 'build.token'),
451 if not proc.wait() and out:
452 slave_token = out.strip()
459 if slave_token != self._master_token:
460 # Get buildlog for the error message
462 (buildlog, err), proc = self._popen_ssh_command(
463 "cat %(buildlog)s" % {
464 'buildlog' : os.path.join(self.home_path, 'buildlog'),
465 'buildscript' : os.path.join(self.home_path, 'nepi-build.sh'),
472 if self.check_bad_host(buildlog, err):
473 self.node.blacklist()
474 elif self._master and trial < 3 and 'BAD TOKEN' in buildlog or 'BAD TOKEN' in err:
475 # bad sync with master, may try again
476 # but first wait for master
477 self._master.async_setup_wait()
478 self._launch_build(trial+1)
479 return self._do_wait_build(trial+1)
481 return self._do_wait_build(trial+1)
484 self._master_prk = None
485 self._master_puk = None
487 raise RuntimeError, "Failed to set up application %s: "\
488 "build failed, got wrong token from pid %s/%s "\
489 "(expected %r, got %r), see buildlog at %s:\n%s" % (
490 self.home_path, pid, ppid, self._master_token, slave_token, self.node.hostname, buildlog)
493 self._master_prk = None
494 self._master_puk = None
496 self._logger.info("Built %s at %s", self, self.node.hostname)
498 def _do_kill_build(self):
499 pid = self._build_pid
500 ppid = self._build_ppid
503 self._logger.info("Killing build of %s", self)
506 host = self.node.hostname,
508 user = self.node.slicename,
510 ident_key = self.node.ident_path,
511 hostip = self.node.hostip
515 def _do_build_master(self):
516 if not self.sources and not self.build and not self.buildDepends:
520 sources = self.sources.split(' ')
526 "%s@%s:%s" % (self.node.slicename, self.node.hostname,
527 os.path.join(self.home_path,'.'),)
529 except RuntimeError, e:
530 raise RuntimeError, "Failed upload source file %r: %s %s" \
531 % (sources, e.args[0], e.args[1],)
533 buildscript = cStringIO.StringIO()
535 buildscript.write("(\n")
537 if self.buildDepends:
538 # Install build dependencies
540 "sudo -S yum -y install %(packages)s\n" % {
541 'packages' : self.buildDepends
549 "mkdir -p build && ( cd build && ( %(command)s ) )\n" % {
550 'command' : self._replace_paths(self.build),
551 'home' : server.shell_escape(self.home_path),
556 buildscript.write("tar czf build.tar.gz build\n")
559 buildscript.write("echo %(master_token)s > build.token ) ; echo %(master_token)s > build.token.retcode" % {
560 'master_token' : server.shell_escape(self._master_token)
567 def _do_install(self):
569 self._logger.info("Installing %s at %s", self, self.node.hostname)
571 # Install application
573 self._popen_ssh_command(
574 "cd %(home)s && cd build && ( %(command)s ) > ${HOME}/%(home)s/installlog 2>&1 || ( tail ${HOME}/%(home)s/{install,build}log >&2 && false )" % \
576 'command' : self._replace_paths(self.install),
577 'home' : server.shell_escape(self.home_path),
580 except RuntimeError, e:
581 if self.check_bad_host(e.args[0], e.args[1]):
582 self.node.blacklist()
583 raise RuntimeError, "Failed install build sources: %s %s" % (e.args[0], e.args[1],)
585 def set_master(self, master):
586 self._master = master
588 def install_keys(self, prk, puk, passphrase):
590 self._master_passphrase = passphrase
591 self._master_prk = prk
592 self._master_puk = puk
593 self._master_prk_name = os.path.basename(prk.name)
594 self._master_puk_name = os.path.basename(puk.name)
596 def _do_install_keys(self):
597 prk = self._master_prk
598 puk = self._master_puk
602 [ prk.name, puk.name ],
603 '%s@%s:%s' % (self.node.slicename, self.node.hostname, self.home_path )
605 except RuntimeError, e:
606 raise RuntimeError, "Failed to set up application deployment keys: %s %s" \
607 % (e.args[0], e.args[1],)
611 cStringIO.StringIO('%s,%s %s\n' % (
612 self._master.node.hostname, self._master.node.hostip,
613 self._master.node.server_key)),
614 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
615 os.path.join(self.home_path,"master_known_hosts") )
617 except RuntimeError, e:
618 raise RuntimeError, "Failed to set up application deployment keys: %s %s" \
619 % (e.args[0], e.args[1],)
623 # make sure there's no leftover build processes
624 self._do_kill_build()
627 self._master_prk = None
628 self._master_puk = None
631 def _popen_scp(self, src, dst, retry = 3):
634 (out,err),proc = server.popen_scp(
639 ident_key = self.node.ident_path,
640 server_key = self.node.server_key
643 if server.eintr_retry(proc.wait)():
644 raise RuntimeError, (out, err)
645 return (out, err), proc
654 def _popen_ssh_command(self, command, retry = 0, noerrors=False, timeout=None):
655 (out,err),proc = server.popen_ssh_command(
657 host = self.node.hostname,
659 user = self.node.slicename,
661 ident_key = self.node.ident_path,
662 server_key = self.node.server_key,
667 if server.eintr_retry(proc.wait)():
669 raise RuntimeError, (out, err)
670 return (out, err), proc
672 class Application(Dependency):
674 An application also has dependencies, but also a command to be ran and monitored.
676 It adds the output of that command as traces.
679 TRACES = ('stdout','stderr','buildlog', 'output')
681 def __init__(self, api=None):
682 super(Application,self).__init__(api)
693 # Those are filled when the app is started
694 # Having both pid and ppid makes it harder
695 # for pid rollover to induce tracking mistakes
696 self._started = False
700 # Do not add to the python path of nodes
701 self.add_to_path = False
704 return "%s<command:%s%s>" % (
705 self.__class__.__name__,
706 "sudo " if self.sudo else "",
711 self._logger.info("Starting %s", self)
713 # Create shell script with the command
714 # This way, complex commands and scripts can be ran seamlessly
716 command = cStringIO.StringIO()
717 command.write('export PYTHONPATH=$PYTHONPATH:%s\n' % (
718 ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.node.pythonpath])
720 command.write('export PATH=$PATH:%s\n' % (
721 ':'.join(["${HOME}/"+server.shell_escape(s) for s in self.node.pythonpath])
724 for envkey, envvals in self.node.env.iteritems():
725 for envval in envvals:
726 command.write('export %s=%s\n' % (envkey, envval))
727 command.write(self.command)
733 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
734 os.path.join(self.home_path, "app.sh"))
736 except RuntimeError, e:
737 raise RuntimeError, "Failed to set up application: %s %s" \
738 % (e.args[0], e.args[1],)
740 # Start process in a "daemonized" way, using nohup and heavy
741 # stdin/out redirection to avoid connection issues
742 (out,err),proc = rspawn.remote_spawn(
743 self._replace_paths("bash ./app.sh"),
746 home = self.home_path,
747 stdin = 'stdin' if self.stdin is not None else '/dev/null',
748 stdout = 'stdout' if self.stdout else '/dev/null',
749 stderr = 'stderr' if self.stderr else '/dev/null',
752 host = self.node.hostname,
754 user = self.node.slicename,
756 ident_key = self.node.ident_path,
757 server_key = self.node.server_key
761 if self.check_bad_host(out, err):
762 self.node.blacklist()
763 raise RuntimeError, "Failed to set up application: %s %s" % (out,err,)
768 # Assuming the application is running on PlanetLab,
769 # proper pidfiles should be present at the app's home path.
770 # So we mark this application as started, and check the pidfiles
776 # NOTE: wait a bit for the pidfile to be created
777 if self._started and not self._pid or not self._ppid:
778 pidtuple = rspawn.remote_check_pid(
779 os.path.join(self.home_path,'pid'),
780 host = self.node.hostname,
782 user = self.node.slicename,
784 ident_key = self.node.ident_path,
785 server_key = self.node.server_key
789 self._pid, self._ppid = pidtuple
793 if not self._started:
794 return AS.STATUS_NOT_STARTED
795 elif not self._pid or not self._ppid:
796 return AS.STATUS_NOT_STARTED
798 status = rspawn.remote_status(
799 self._pid, self._ppid,
800 host = self.node.hostname,
802 user = self.node.slicename,
804 ident_key = self.node.ident_path,
805 server_key = self.node.server_key
808 if status is rspawn.NOT_STARTED:
809 return AS.STATUS_NOT_STARTED
810 elif status is rspawn.RUNNING:
811 return AS.STATUS_RUNNING
812 elif status is rspawn.FINISHED:
813 return AS.STATUS_FINISHED
816 return AS.STATUS_NOT_STARTED
819 status = self.status()
820 if status == AS.STATUS_RUNNING:
821 # kill by ppid+pid - SIGTERM first, then try SIGKILL
823 self._pid, self._ppid,
824 host = self.node.hostname,
826 user = self.node.slicename,
828 ident_key = self.node.ident_path,
829 server_key = self.node.server_key,
832 self._logger.info("Killed %s", self)
835 class NepiDependency(Dependency):
837 This dependency adds nepi itself to the python path,
838 so that you may run testbeds within PL nodes.
841 # Class attribute holding a *weak* reference to the shared NEPI tar file
842 # so that they may share it. Don't operate on the file itself, it would
843 # be a mess, just use its path.
844 _shared_nepi_tar = None
846 def __init__(self, api = None):
847 super(NepiDependency, self).__init__(api)
851 self.depends = 'python python-ipaddr python-setuptools'
853 # our sources are in our ad-hoc tarball
854 self.sources = self.tarball.name
856 tarname = os.path.basename(self.tarball.name)
858 # it's already built - just move the tarball into place
859 self.build = "mv -f ${SOURCES}/%s ." % (tarname,)
861 # unpack it into sources, and we're done
862 self.install = "tar xzf ${BUILD}/%s -C .." % (tarname,)
866 if self._tarball is None:
867 shared_tar = self._shared_nepi_tar and self._shared_nepi_tar()
868 if shared_tar is not None:
869 self._tarball = shared_tar
871 # Build an ad-hoc tarball
876 shared_tar = tempfile.NamedTemporaryFile(prefix='nepi-src-', suffix='.tar.gz')
878 proc = subprocess.Popen(
879 ["tar", "czf", shared_tar.name,
880 '-C', os.path.join(os.path.dirname(os.path.dirname(nepi.__file__)),'.'),
882 stdout = open("/dev/null","w"),
883 stdin = open("/dev/null","r"))
886 raise RuntimeError, "Failed to create nepi tarball"
888 self._tarball = self._shared_nepi_tar = shared_tar
892 class NS3Dependency(Dependency):
894 This dependency adds NS3 libraries to the library paths,
895 so that you may run the NS3 testbed within PL nodes.
897 You'll also need the NepiDependency.
900 def __init__(self, api = None):
901 super(NS3Dependency, self).__init__(api)
903 self.buildDepends = 'make waf gcc gcc-c++ gccxml unzip bzr'
905 # We have to download the sources, untar, build...
906 pygccxml_source_url = "http://leaseweb.dl.sourceforge.net/project/pygccxml/pygccxml/pygccxml-1.0/pygccxml-1.0.0.zip"
907 ns3_source_url = "http://nepi.pl.sophia.inria.fr/code/nepi-ns3.13/archive/tip.tar.gz"
908 passfd_source_url = "http://nepi.pl.sophia.inria.fr/code/python-passfd/archive/tip.tar.gz"
910 pybindgen_version = "797"
915 " python -c 'import pygccxml, pybindgen, passfd' && "
916 " test -f lib/ns/_core.so && "
917 " test -f lib/ns/__init__.py && "
918 " test -f lib/ns/core.py && "
919 " test -f lib/libns3-core.so && "
920 " LD_LIBRARY_PATH=lib PYTHONPATH=lib python -c 'import ns.core' "
922 # Not working, rebuild
923 # Archive SHA1 sums to check
924 "echo '7158877faff2254e6c094bf18e6b4283cac19137 pygccxml-1.0.0.zip' > archive_sums.txt && "
925 " ( " # check existing files
926 " sha1sum -c archive_sums.txt && "
927 " test -f passfd-src.tar.gz && "
928 " test -f ns3-src.tar.gz "
929 " ) || ( " # nope? re-download
930 " rm -rf pybindgen pygccxml-1.0.0.zip passfd-src.tar.gz ns3-src.tar.gz && "
931 " bzr checkout lp:pybindgen -r %(pybindgen_version)s && " # continue, to exploit the case when it has already been dl'ed
932 " wget -q -c -O pygccxml-1.0.0.zip %(pygccxml_source_url)s && "
933 " wget -q -c -O passfd-src.tar.gz %(passfd_source_url)s && "
934 " wget -q -c -O ns3-src.tar.gz %(ns3_source_url)s && "
935 " sha1sum -c archive_sums.txt " # Check SHA1 sums when applicable
937 "unzip -n pygccxml-1.0.0.zip && "
938 "mkdir -p ns3-src && "
939 "mkdir -p passfd-src && "
940 "tar xzf ns3-src.tar.gz --strip-components=1 -C ns3-src && "
941 "tar xzf passfd-src.tar.gz --strip-components=1 -C passfd-src && "
942 "rm -rf target && " # mv doesn't like unclean targets
943 "mkdir -p target && "
944 "cd pygccxml-1.0.0 && "
945 "rm -rf unittests docs && " # pygccxml has ~100M of unit tests - excessive - docs aren't needed either
946 "python setup.py build && "
947 "python setup.py install --install-lib ${BUILD}/target && "
948 "python setup.py clean && "
949 "cd ../pybindgen && "
950 "export PYTHONPATH=$PYTHONPATH:${BUILD}/target && "
951 "./waf configure --prefix=${BUILD}/target -d release && "
955 "mv -f ${BUILD}/target/lib/python*/site-packages/pybindgen ${BUILD}/target/. && "
956 "rm -rf ${BUILD}/target/lib && "
957 "cd ../passfd-src && "
958 "python setup.py build && "
959 "python setup.py install --install-lib ${BUILD}/target && "
960 "python setup.py clean && "
962 "./waf configure --prefix=${BUILD}/target --with-pybindgen=../pybindgen-src -d release --disable-examples --disable-tests && "
965 "rm -f ${BUILD}/target/lib/*.so && "
966 "cp -a ${BUILD}/ns3-src/build/libns3*.so ${BUILD}/target/lib && "
967 "cp -a ${BUILD}/ns3-src/build/bindings/python/ns ${BUILD}/target/lib &&"
971 pybindgen_version = server.shell_escape(pybindgen_version),
972 pygccxml_source_url = server.shell_escape(pygccxml_source_url),
973 ns3_source_url = server.shell_escape(ns3_source_url),
974 passfd_source_url = server.shell_escape(passfd_source_url),
977 # Just move ${BUILD}/target
981 " python -c 'import pygccxml, pybindgen, passfd' && "
982 " test -f lib/ns/_core.so && "
983 " test -f lib/ns/__init__.py && "
984 " test -f lib/ns/core.py && "
985 " test -f lib/libns3-core.so && "
986 " LD_LIBRARY_PATH=lib PYTHONPATH=lib python -c 'import ns.core' "
988 # Not working, reinstall
989 "test -d ${BUILD}/target && "
990 "[[ \"x\" != \"x$(find ${BUILD}/target -mindepth 1 -print -quit)\" ]] &&"
991 "( for i in ${BUILD}/target/* ; do rm -rf ${SOURCES}/${i##*/} ; done ) && " # mv doesn't like unclean targets
992 "mv -f ${BUILD}/target/* ${SOURCES}"
996 # Set extra environment paths
997 self.env['NEPI_NS3BINDINGS'] = "${SOURCES}/lib"
998 self.env['NEPI_NS3LIBRARY'] = "${SOURCES}/lib"
1002 if self._tarball is None:
1003 shared_tar = self._shared_nepi_tar and self._shared_nepi_tar()
1004 if shared_tar is not None:
1005 self._tarball = shared_tar
1007 # Build an ad-hoc tarball
1012 shared_tar = tempfile.NamedTemporaryFile(prefix='nepi-src-', suffix='.tar.gz')
1014 proc = subprocess.Popen(
1015 ["tar", "czf", shared_tar.name,
1016 '-C', os.path.join(os.path.dirname(os.path.dirname(nepi.__file__)),'.'),
1018 stdout = open("/dev/null","w"),
1019 stdin = open("/dev/null","r"))
1022 raise RuntimeError, "Failed to create nepi tarball"
1024 self._tarball = self._shared_nepi_tar = shared_tar
1026 return self._tarball
1028 class YumDependency(Dependency):
1030 This dependency is an internal helper class used to
1031 efficiently distribute yum-downloaded rpms.
1033 It temporarily sets the yum cache as persistent in the
1034 build master, and installs all the required packages.
1036 The rpm packages left in the yum cache are gathered and
1037 distributed by the underlying Dependency in an efficient
1038 manner. Build slaves will then install those rpms back in
1039 the cache before issuing the install command.
1041 When packages have been installed already, nothing but an
1042 empty tar is distributed.
1045 # Class attribute holding a *weak* reference to the shared NEPI tar file
1046 # so that they may share it. Don't operate on the file itself, it would
1047 # be a mess, just use its path.
1048 _shared_nepi_tar = None
1050 def _build_get(self):
1051 # canonical representation of dependencies
1052 depends = ' '.join( sorted( (self.depends or "").split(' ') ) )
1054 # download rpms and pack into a tar archive
1056 "sudo -S nice yum -y makecache && "
1057 "sudo -S sed -i -r 's/keepcache *= *0/keepcache=1/' /etc/yum.conf && "
1059 "sudo -S nice yum -y install %s ; "
1060 "rm -f ${BUILD}/packages.tar ; "
1061 "tar -C /var/cache/yum -rf ${BUILD}/packages.tar $(cd /var/cache/yum ; find -iname '*.rpm')"
1062 " ) || /bin/true ) && "
1063 "sudo -S sed -i -r 's/keepcache *= *1/keepcache=0/' /etc/yum.conf && "
1064 "( sudo -S nice yum -y clean packages || /bin/true ) "
1066 def _build_set(self, value):
1069 build = property(_build_get, _build_set)
1071 def _install_get(self):
1072 # canonical representation of dependencies
1073 depends = ' '.join( sorted( (self.depends or "").split(' ') ) )
1075 # unpack cached rpms into yum cache, install, and cleanup
1077 "sudo -S tar -k --keep-newer-files -C /var/cache/yum -xf packages.tar && "
1078 "sudo -S nice yum -y install %s && "
1079 "( sudo -S nice yum -y clean packages || /bin/true ) "
1081 def _install_set(self, value):
1084 install = property(_install_get, _install_set)
1086 def check_bad_host(self, out, err):
1087 badre = re.compile(r'(?:'
1088 r'The GPG keys listed for the ".*" repository are already installed but they are not correct for this package'
1089 r'|Error: Cannot retrieve repository metadata (repomd.xml) for repository: .*[.] Please verify its path and try again'
1090 r'|Error: disk I/O error'
1091 r'|MASTER NODE UNREACHABLE'
1094 return badre.search(out) or badre.search(err) or self.node.check_bad_host(out,err)
1097 class CCNxDaemon(Application):
1099 An application also has dependencies, but also a command to be ran and monitored.
1101 It adds the output of that command as traces.
1104 def __init__(self, api=None):
1105 super(CCNxDaemon,self).__init__(api)
1108 self.ccnroutes = None
1109 self.ccnsources = None
1111 self.default_ccnx_sources = "http://www.ccnx.org/releases/ccnx-0.5.1.tar.gz"
1112 self.buildDepends = 'make gcc development-tools openssl-devel expat-devel libpcap-devel libxml2-devel'
1114 self.default_build = (
1117 " test -d ccnx-src/build/bin "
1119 # Not working, rebuild
1121 " mkdir -p ccnx-src && "
1122 " ( %(not_custom_source)s &&"
1123 " wget -q -c -O ccnx-src.tar.gz %(ccnx_source_url)s &&"
1124 " tar xf ccnx-src.tar.gz --strip-components=1 -C ccnx-src "
1126 " tar xf %(user_ccnx_tar)s --strip-components=1 -C ccnx-src "
1130 "mkdir -p build/include &&"
1131 "mkdir -p build/lib &&"
1132 "mkdir -p build/bin &&"
1134 "INSTALL_BASE=$I ./configure &&"
1135 "make && make install"
1138 self.default_install = (
1141 " test -d ${SOURCES}/bin "
1143 " test -d ${BUILD}/ccnx-src/build/bin && "
1144 " cp -r ${BUILD}/ccnx-src/build/bin ${SOURCES}"
1148 self.env['PATH'] = "$PATH:${SOURCES}/bin"
1151 # setting ccn sources
1152 not_custom_source = "true"
1155 self.sources = self.ccnsources
1156 sources = "${SOURCES}/%s" % os.path.basename(self.ccnsources)
1157 not_custom_source = "false"
1160 self.build = self.default_build % dict(
1161 ccnx_source_url = server.shell_escape(self.default_ccnx_sources),
1162 not_custom_source = server.shell_escape(not_custom_source),
1163 user_ccnx_tar = sources
1166 if not self.install:
1167 self.install = self.default_install
1169 super(CCNxDaemon, self).setup()
1172 # configure ccn routes
1175 routes = map(lambda route: "ccndc add ccnx:/ %s" % route,
1176 self.ccnroutes.split("|"))
1177 routes = "; " + " ; ".join(routes)
1178 self.command = "ccndstart %s" % routes
1180 # Start will be invoked in prestart step
1181 super(CCNxDaemon, self).start()
1184 self._logger.info("Killing %s", self)
1186 cmd = self._replace_paths("${SOURCES}/bin/ccndstop")
1187 command = cStringIO.StringIO()
1194 '%s@%s:%s' % (self.node.slicename, self.node.hostname,
1195 os.path.join(self.home_path, "kill.sh"))
1197 except RuntimeError, e:
1198 raise RuntimeError, "Failed to kill ccndxdaemon: %s %s" \
1199 % (e.args[0], e.args[1],)
1202 script = "bash ./kill.sh"
1203 (out,err),proc = rspawn.remote_spawn(
1205 pidfile = 'kill-pid',
1206 home = self.home_path,
1207 stdin = '/dev/null',
1209 stderr = rspawn.STDOUT,
1211 host = self.node.hostname,
1213 user = self.node.slicename,
1215 ident_key = self.node.ident_path,
1216 server_key = self.node.server_key,
1217 hostip = self.node.hostip,
1221 raise RuntimeError, "Failed to kill cnnxdaemon: %s %s" % (out,err,)
1223 super(CCNxDaemon, self).kill()