3 """Automated tests of the apt-p2p functionality.
5 This script runs several automatic tests of some of the functionality in
8 @type tests: C{dictionary}
9 @var tests: all of the tests that can be run.
10 The keys are the test names (strings) which are used on the command-line
11 to identify the tests (can not be 'all' or 'help'). The values are tuples
12 with four elements: a description of the test (C{string}), the bootstrap
13 nodes to start (C{dictionary}), the downloaders to start (C{dictionary},
14 and the apt-get commands to run (C{list}).
16 The bootstrap nodes keys are integers, which must be in the range 1-9.
17 The values are the dictionary of keyword options to pass to the function
18 that starts the bootstrap node (see L{start_bootstrap} below).
20 The downloaders keys are also integers in the range 1-99. The values are
21 the dictionary of keyword options to pass to the function
22 that starts the downloader node (see L{start_downloader} below).
24 The apt-get commands' list elements are tuples with 2 elements: the
25 downloader to run the command on, and the list of command-line
26 arguments to specify to the apt-get program.
29 @var CWD: the working directory the script was run from
30 @type apt_conf_template: C{string}
31 @var apt_conf_template: the template to use for the apt.conf file
35 from time import sleep, time
36 import sys, os, signal
37 from traceback import print_exc
38 from os.path import exists
40 tests = {'1': ('Start a single bootstrap and downloader, test updating and downloading ' +
45 (1, ['install', 'aboot-base']),
46 (1, ['install', 'aap-doc']),
47 (1, ['install', 'ada-reference-manual']),
48 (1, ['install', 'aspectj-doc']),
49 (1, ['install', 'fop-doc']),
50 (1, ['install', 'asis-doc']),
51 (1, ['install', 'bison-doc']),
52 (1, ['install', 'crash-whitepaper']),
55 '2': ('Start a single bootstrap and 2 downloaders to test downloading from a peer.',
61 (1, ['install', 'aboot-base']),
62 (2, ['install', 'aboot-base']),
63 (1, ['install', 'aap-doc']),
64 (1, ['install', 'ada-reference-manual']),
65 (1, ['install', 'fop-doc']),
66 (1, ['install', 'bison-doc']),
67 (1, ['install', 'crash-whitepaper']),
68 (2, ['install', 'aap-doc']),
69 (2, ['install', 'ada-reference-manual']),
70 (2, ['install', 'fop-doc']),
71 (2, ['install', 'bison-doc']),
72 (2, ['install', 'crash-whitepaper']),
75 '3': ('Start a single bootstrap and 6 downloaders, to test downloading' +
76 ' speeds from each other.',
85 (1, ['install', 'aboot-base']),
86 (1, ['install', 'ada-reference-manual']),
87 (1, ['install', 'fop-doc']),
88 (1, ['install', 'crash-whitepaper']),
90 (2, ['install', 'aboot-base']),
91 (2, ['install', 'ada-reference-manual']),
92 (2, ['install', 'fop-doc']),
93 (2, ['install', 'crash-whitepaper']),
95 (3, ['install', 'aboot-base']),
96 (3, ['install', 'ada-reference-manual']),
97 (3, ['install', 'fop-doc']),
98 (3, ['install', 'crash-whitepaper']),
100 (4, ['install', 'aboot-base']),
101 (4, ['install', 'ada-reference-manual']),
102 (4, ['install', 'fop-doc']),
103 (4, ['install', 'crash-whitepaper']),
105 (5, ['install', 'aboot-base']),
106 (5, ['install', 'ada-reference-manual']),
107 (5, ['install', 'fop-doc']),
108 (5, ['install', 'crash-whitepaper']),
110 (6, ['install', 'aboot-base']),
111 (6, ['install', 'ada-reference-manual']),
112 (6, ['install', 'fop-doc']),
113 (6, ['install', 'crash-whitepaper']),
116 '4': ('Start a single bootstrap and 1 downloader, requesting the same' +
117 ' packages multiple times to test caching.',
121 (1, ['install', 'aboot-base']),
122 (1, ['install', 'ada-reference-manual']),
123 (1, ['install', 'fop-doc']),
124 (1, ['install', 'crash-whitepaper']),
126 (1, ['install', 'aboot-base']),
127 (1, ['install', 'ada-reference-manual']),
128 (1, ['install', 'fop-doc']),
129 (1, ['install', 'crash-whitepaper']),
131 (1, ['install', 'aboot-base']),
132 (1, ['install', 'ada-reference-manual']),
133 (1, ['install', 'fop-doc']),
134 (1, ['install', 'crash-whitepaper']),
137 '5': ('Start a single bootstrap and 6 downloaders, update all to test' +
138 ' that they can all see each other.',
140 {1: ([], {'suites': 'contrib non-free'}),
141 2: ([], {'suites': 'contrib non-free'}),
142 3: ([], {'suites': 'contrib non-free'}),
143 4: ([], {'suites': 'contrib non-free'}),
144 5: ([], {'suites': 'contrib non-free'}),
145 6: ([], {'suites': 'contrib non-free'})},
154 '6': ('Test caching with multiple apt-get updates.',
163 '7': ('Test pipelining of multiple simultaneous downloads.',
167 (1, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual',
168 'aspectj-doc', 'fop-doc', 'asis-doc',
169 'bison-doc', 'crash-whitepaper',
170 'bash-doc', 'apt-howto-common', 'autotools-dev',
171 'aptitude-doc-en', 'asr-manpages',
172 'atomix-data', 'alcovebook-sgml-doc',
173 'afbackup-common', 'airstrike-common',
177 '8': ('Test pipelining of multiple simultaneous downloads with many peers.',
186 (1, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual',
187 'aspectj-doc', 'fop-doc', 'asis-doc',
188 'bison-doc', 'crash-whitepaper',
189 'bash-doc', 'apt-howto-common', 'autotools-dev',
190 'aptitude-doc-en', 'asr-manpages',
191 'atomix-data', 'alcovebook-sgml-doc',
192 'afbackup-common', 'airstrike-common',
195 (2, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual',
196 'aspectj-doc', 'fop-doc', 'asis-doc',
197 'bison-doc', 'crash-whitepaper',
198 'bash-doc', 'apt-howto-common', 'autotools-dev',
199 'aptitude-doc-en', 'asr-manpages',
200 'atomix-data', 'alcovebook-sgml-doc',
201 'afbackup-common', 'airstrike-common',
204 (3, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual',
205 'aspectj-doc', 'fop-doc', 'asis-doc',
206 'bison-doc', 'crash-whitepaper',
207 'bash-doc', 'apt-howto-common', 'autotools-dev',
208 'aptitude-doc-en', 'asr-manpages',
209 'atomix-data', 'alcovebook-sgml-doc',
210 'afbackup-common', 'airstrike-common',
213 (4, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual',
214 'aspectj-doc', 'fop-doc', 'asis-doc',
215 'bison-doc', 'crash-whitepaper',
216 'bash-doc', 'apt-howto-common', 'autotools-dev',
217 'aptitude-doc-en', 'asr-manpages',
218 'atomix-data', 'alcovebook-sgml-doc',
219 'afbackup-common', 'airstrike-common',
222 (5, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual',
223 'aspectj-doc', 'fop-doc', 'asis-doc',
224 'bison-doc', 'crash-whitepaper',
225 'bash-doc', 'apt-howto-common', 'autotools-dev',
226 'aptitude-doc-en', 'asr-manpages',
227 'atomix-data', 'alcovebook-sgml-doc',
228 'afbackup-common', 'airstrike-common',
231 (6, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual',
232 'aspectj-doc', 'fop-doc', 'asis-doc',
233 'bison-doc', 'crash-whitepaper',
234 'bash-doc', 'apt-howto-common', 'autotools-dev',
235 'aptitude-doc-en', 'asr-manpages',
236 'atomix-data', 'alcovebook-sgml-doc',
237 'afbackup-common', 'airstrike-common',
241 '9': ('Start a single bootstrap and 6 downloaders and test downloading' +
242 ' a very large file.',
251 (1, ['install', 'kde-icons-oxygen']),
253 (2, ['install', 'kde-icons-oxygen']),
255 (3, ['install', 'kde-icons-oxygen']),
257 (4, ['install', 'kde-icons-oxygen']),
259 (5, ['install', 'kde-icons-oxygen']),
261 (6, ['install', 'kde-icons-oxygen']),
264 'a': ('Test pipelining and caching, can also interrupt or restart to test resuming.',
265 {1: {'clean': False}},
266 {1: {'clean': False},
267 2: {'clean': False}},
268 [(1, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual',
269 'aspectj-doc', 'fop-doc', 'asis-doc',
270 'bison-doc', 'crash-whitepaper',
271 'bash-doc', 'apt-howto-common', 'autotools-dev',
272 'aptitude-doc-en', 'asr-manpages',
273 'atomix-data', 'alcovebook-sgml-doc',
274 'afbackup-common', 'airstrike-common',
278 (1, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual',
279 'aspectj-doc', 'fop-doc', 'asis-doc',
280 'bison-doc', 'crash-whitepaper',
281 'bash-doc', 'apt-howto-common', 'autotools-dev',
282 'aptitude-doc-en', 'asr-manpages',
283 'atomix-data', 'alcovebook-sgml-doc',
284 'afbackup-common', 'airstrike-common',
286 (1, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual',
287 'aspectj-doc', 'fop-doc', 'asis-doc',
288 'bison-doc', 'crash-whitepaper',
289 'bash-doc', 'apt-howto-common', 'autotools-dev',
290 'aptitude-doc-en', 'asr-manpages',
291 'atomix-data', 'alcovebook-sgml-doc',
292 'afbackup-common', 'airstrike-common',
294 (2, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual',
295 'aspectj-doc', 'fop-doc', 'asis-doc',
296 'bison-doc', 'crash-whitepaper',
297 'bash-doc', 'apt-howto-common', 'autotools-dev',
298 'aptitude-doc-en', 'asr-manpages',
299 'atomix-data', 'alcovebook-sgml-doc',
300 'afbackup-common', 'airstrike-common',
304 (2, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual',
305 'aspectj-doc', 'fop-doc', 'asis-doc',
306 'bison-doc', 'crash-whitepaper',
307 'bash-doc', 'apt-howto-common', 'autotools-dev',
308 'aptitude-doc-en', 'asr-manpages',
309 'atomix-data', 'alcovebook-sgml-doc',
310 'afbackup-common', 'airstrike-common',
312 (2, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual',
313 'aspectj-doc', 'fop-doc', 'asis-doc',
314 'bison-doc', 'crash-whitepaper',
315 'bash-doc', 'apt-howto-common', 'autotools-dev',
316 'aptitude-doc-en', 'asr-manpages',
317 'atomix-data', 'alcovebook-sgml-doc',
318 'afbackup-common', 'airstrike-common',
322 'b': ('Start 2 downloaders and test source downloads.',
324 {1: {'types': ['deb-src']},
325 2: {'types': ['deb-src']}},
328 (1, ['source', 'aboot-base']),
329 (2, ['source', 'aboot-base']),
330 (1, ['source', 'aap-doc']),
331 (1, ['source', 'ada-reference-manual']),
332 (1, ['source', 'fop-doc']),
333 (1, ['source', 'bison-doc']),
334 (1, ['source', 'crash-whitepaper']),
335 (2, ['source', 'aap-doc']),
336 (2, ['source', 'ada-reference-manual']),
337 (2, ['source', 'fop-doc']),
338 (2, ['source', 'bison-doc']),
339 (2, ['source', 'crash-whitepaper']),
344 assert 'all' not in tests
345 assert 'help' not in tests
348 apt_conf_template = """
350 // Location of the state dir
355 userstatus "status.user";
356 cdroms "cdroms.list";
359 // Location of the cache dir
360 Cache "var/cache/apt/" {
361 Archives "archives/";
362 srcpkgcache "srcpkgcache.bin";
363 pkgcache "pkgcache.bin";
368 SourceList "sources.list";
370 Preferences "preferences";
374 // Locations of binaries
376 methods "/usr/lib/apt/methods/";
379 dpkg "/usr/bin/dpkg --simulate";
380 dpkg-source "/usr/bin/dpkg-source";
381 dpkg-buildpackage "/usr/bin/dpkg-buildpackage";
382 apt-get "/usr/bin/apt-get";
383 apt-cache "/usr/bin/apt-cache";
387 /* Options you can set to see some debugging text They correspond to names
388 of classes in the source code */
391 pkgProblemResolver "false";
392 pkgDepCache::AutoInstall "false"; // what packages apt install to satify dependencies
394 pkgAcquire::Worker "false";
395 pkgAcquire::Auth "false";
397 pkgDPkgProgressReporting "false";
398 pkgOrderList "false";
401 pkgInitialize "false"; // This one will dump the configuration space
403 Acquire::Ftp "false"; // Show ftp command traffic
404 Acquire::Http "false"; // Show http command traffic
405 Acquire::gpgv "false"; // Show the gpgv traffic
406 aptcdrom "false"; // Show found package files
411 apt_p2p_conf_template = """
414 # Port to listen on for all requests (TCP and UDP)
417 # The rate to limit sending data to peers to, in KBytes/sec.
418 # Set this to 0 to not limit the upload bandwidth.
421 # The minimum number of peers before the mirror is not used.
422 # If there are fewer peers than this for a file, the mirror will also be
423 # used to speed up the download. Set to 0 to never use the mirror if
425 MIN_DOWNLOAD_PEERS = 3
427 # Directory to store the downloaded files in
428 CACHE_DIR = %(CACHE_DIR)s
430 # Other directories containing packages to share with others
431 # WARNING: all files in these directories will be hashed and available
432 # for everybody to download
435 # Whether it's OK to use an IP addres from a known local/private range
438 # Whether a remote peer can access the statistics page
441 # Unload the packages cache after an interval of inactivity this long.
442 # The packages cache uses a lot of memory, and only takes a few seconds
443 # to reload when a new request arrives.
444 UNLOAD_PACKAGES_CACHE = 5m
446 # Refresh the DHT keys after this much time has passed.
447 # This should be a time slightly less than the DHT's KEY_EXPIRE value.
450 # The user name to try and run as (leave blank to run as current user)
453 # Which DHT implementation to use.
454 # It must be possile to do "from <DHT>.DHT import DHT" to get a class that
455 # implements the IDHT interface.
456 DHT = apt_p2p_Khashmir
458 # Whether to only run the DHT (for providing only a bootstrap node)
459 DHT-ONLY = %(DHT-ONLY)s
462 # bootstrap nodes to contact to join the DHT
463 BOOTSTRAP = %(BOOTSTRAP)s
465 # whether this node is a bootstrap node
466 BOOTSTRAP_NODE = %(BOOTSTRAP_NODE)s
468 # checkpoint every this many seconds
469 CHECKPOINT_INTERVAL = 5m
471 # concurrent xmlrpc calls per find node/value request!
474 # how many hosts to post to
477 # How many values to attempt to retrieve from the DHT.
478 # Setting this to 0 will try and get all values (which could take a while if
479 # a lot of nodes have values). Setting it negative will try to get that
480 # number of results from only the closest STORE_REDUNDANCY nodes to the hash.
481 # The default is a large negative number so all values from the closest
482 # STORE_REDUNDANCY nodes will be retrieved.
483 RETRIEVE_VALUES = -10000
485 # how many times in a row a node can fail to respond before it's booted from the routing table
488 # never ping a node more often than this
489 MIN_PING_INTERVAL = 15m
491 # refresh buckets that haven't been touched in this long
492 BUCKET_STALENESS = 1h
494 # expire entries older than this
497 # Timeout KRPC requests to nodes after this time.
500 # KRPC requests are resent using exponential backoff starting with this delay.
501 # The request will first be resent after the delay set here.
502 # The request will be resent again after twice the delay set here. etc.
503 # e.g. if TIMEOUT is 14 sec., and INITIAL_DELAY is 2 sec., then requests will
504 # be resent at times 0, 2 (2 sec. later), and 6 (4 sec. later), and then will
506 KRPC_INITIAL_DELAY = 2s
508 # whether to spew info about the requests/responses in the protocol
513 """Remove all the files and directories below a top-level one.
516 @param top: the top-level directory to start at
520 for root, dirs, files in os.walk(top, topdown=False):
522 os.remove(os.path.join(root, name))
524 os.rmdir(os.path.join(root, name))
527 """Join together a list of directories into a path string.
529 @type dir: C{list} of C{string}
530 @param dir: the path to join together
532 @return: the joined together path
538 joined = os.path.join(joined, i)
542 """Create all the directories to make a path.
544 @type dir: C{list} of C{string}
545 @param dir: the path to create
548 if not os.path.exists(join(dir)):
549 os.makedirs(join(dir))
552 """Create an empty file.
554 @type path: C{list} of C{string}
555 @param path: the path to create
559 f = open(join(path), 'w')
562 def start(cmd, args, work_dir = None):
563 """Fork and start a background process running.
566 @param cmd: the name of the command to run
567 @type args: C{list} of C{string}
568 @param args: the argument to pass to the command
569 @type work_dir: C{string}
570 @param work_dir: the directory to change to to execute the child process in
571 (optional, defaults to the current directory)
573 @return: the PID of the forked process
577 new_cmd = [cmd] + args
580 pid = os.spawnvp(os.P_NOWAIT, new_cmd[0], new_cmd)
584 """Stop a forked background process that is running.
587 @param pid: the PID of the process to stop
589 @return: the return status code from the child
593 # First try a keyboard interrupt
594 os.kill(pid, signal.SIGINT)
597 (r_pid, r_value) = os.waitpid(pid, os.WNOHANG)
601 # Try a keyboard interrupt again, just in case
602 os.kill(pid, signal.SIGINT)
605 (r_pid, r_value) = os.waitpid(pid, os.WNOHANG)
610 os.kill(pid, signal.SIGTERM)
613 (r_pid, r_value) = os.waitpid(pid, os.WNOHANG)
617 # Finally a kill, don't return until killed
618 os.kill(pid, signal.SIGKILL)
621 (r_pid, r_value) = os.waitpid(pid, os.WNOHANG)
625 def apt_get(num_down, cmd):
626 """Start an apt-get process in the background.
628 The default argument specified to the apt-get invocation are
629 'apt-get -d -q -c <conf_file>'. Any additional arguments (including
630 the apt-get action to use) should be specified.
632 @type num_down: C{int}
633 @param num_down: the number of the downloader to use
634 @type cmd: C{list} of C{string}
635 @param cmd: the arguments to pass to the apt-get process
637 @return: the PID of the background process
641 downloader_dir = down_dir(num_down)
642 rmrf(join([downloader_dir, 'var', 'cache', 'apt', 'archives']))
643 makedirs([downloader_dir, 'var', 'cache', 'apt', 'archives', 'partial'])
645 print '*************** apt-get (' + str(num_down) + ') ' + ' '.join(cmd) + ' ****************'
646 apt_conf = join([down_dir(num_down), 'etc', 'apt', 'apt.conf'])
647 dpkg_status = join([down_dir(num_down), 'var', 'lib', 'dpkg', 'status'])
648 args = ['-d', '-c', apt_conf, '-o', 'Dir::state::status='+dpkg_status] + cmd
649 pid = start('apt-get', args, downloader_dir)
652 def bootstrap_address(num_boot):
653 """Determine the bootstrap address to use for a node.
655 @type num_boot: C{int}
656 @param num_boot: the number of the bootstrap node
658 @return: the bootstrap address to use
662 return 'localhost:1' + str(num_boot) + '969'
664 def down_dir(num_down):
665 """Determine the working directory to use for a downloader.
667 @type num_down: C{int}
668 @param num_down: the number of the downloader
670 @return: the downloader's directory
674 return os.path.join(CWD,'downloader' + str(num_down))
676 def boot_dir(num_boot):
677 """Determine the working directory to use for a bootstrap node.
679 @type num_boot: C{int}
680 @param num_boot: the number of the bootstrap node
682 @return: the bootstrap node's directory
686 return os.path.join(CWD,'bootstrap' + str(num_boot))
688 def start_downloader(bootstrap_addresses, num_down, options = {},
689 types = ['deb'], mirror = 'ftp.us.debian.org/debian',
690 suites = 'main contrib non-free', clean = True):
691 """Initialize a new downloader process.
693 The default arguments specified to the downloader invocation are
694 the configuration directory, apt port, minport, maxport and the
696 Any additional arguments needed should be specified by L{options}.
698 @type num_down: C{int}
699 @param num_down: the number of the downloader to use
700 @type options: C{dictionary}
701 @param options: the dictionary of string formatting values for creating
702 the apt-p2p configuration file (see L{apt_p2p_conf_template} above).
703 (optional, defaults to only using the default arguments)
704 @type types: C{list} of C{string}
705 @param types: the type of sources.list line to add
706 (optional, defaults to only 'deb')
707 @type mirror: C{string}
708 @param mirror: the Debian mirror to use
709 (optional, defaults to 'ftp.us.debian.org/debian')
710 @type suites: C{string}
711 @param suites: space separated list of suites to download
712 (optional, defaults to 'main contrib non-free')
713 @type clean: C{boolean}
714 @param clean: whether to remove any previous downloader files
715 (optional, defaults to removing them)
717 @return: the PID of the downloader process
721 assert num_down < 100
723 print '************************** Starting Downloader ' + str(num_down) + ' **************************'
725 downloader_dir = down_dir(num_down)
733 # Create the directory structure needed by apt
734 makedirs([downloader_dir, 'etc', 'apt', 'apt.conf.d'])
735 makedirs([downloader_dir, 'var', 'lib', 'apt', 'lists', 'partial'])
736 makedirs([downloader_dir, 'var', 'lib', 'dpkg'])
737 rmrf(join([downloader_dir, 'var', 'cache', 'apt', 'archives']))
738 makedirs([downloader_dir, 'var', 'cache', 'apt', 'archives', 'partial'])
739 touch([downloader_dir, 'var', 'lib', 'apt', 'lists', 'lock'])
740 touch([downloader_dir, 'var', 'lib', 'dpkg', 'lock'])
741 touch([downloader_dir, 'var', 'lib', 'dpkg', 'status'])
742 touch([downloader_dir, 'var', 'cache', 'apt', 'archives', 'lock'])
744 if not exists(join([downloader_dir, 'etc', 'apt', 'sources.list'])):
745 # Create apt's config files
746 f = open(join([downloader_dir, 'etc', 'apt', 'sources.list']), 'w')
748 f.write('%s http://localhost:1%02d77/%s/ unstable %s\n' % (type, num_down, mirror, suites))
751 if not exists(join([downloader_dir, 'etc', 'apt', 'apt.conf'])):
752 f = open(join([downloader_dir, 'etc', 'apt', 'apt.conf']), 'w')
753 f.write('Dir "' + downloader_dir + '"')
754 f.write(apt_conf_template)
757 defaults = {'PORT': '1%02d77' % num_down,
758 'CACHE_DIR': downloader_dir,
760 'BOOTSTRAP': bootstrap_addresses,
761 'BOOTSTRAP_NODE': 'no'}
764 defaults[k] = options[k]
765 f = open(join([downloader_dir, 'apt-p2p.conf']), 'w')
766 f.write(apt_p2p_conf_template % defaults)
769 pid = start('python', [join([sys.path[0], 'apt-p2p.py']),
770 '--config-file=' + join([downloader_dir, 'apt-p2p.conf']),
771 '--log-file=' + join([downloader_dir, 'apt-p2p.log']),],
775 def start_bootstrap(bootstrap_addresses, num_boot, options = [], clean = True):
776 """Initialize a new bootstrap node process.
778 The default arguments specified to the apt-p2p invocation are
779 the state file and port to use. Any additional arguments needed
780 should be specified by L{options}.
782 @type num_boot: C{int}
783 @param num_boot: the number of the bootstrap node to use
784 @type options: C{list} of C{string}
785 @param options: the arguments to pass to the bootstrap node
786 (optional, defaults to only using the default arguments)
787 @type clean: C{boolean}
788 @param clean: whether to remove any previous bootstrap node files
789 (optional, defaults to removing them)
791 @return: the PID of the downloader process
797 print '************************** Starting Bootstrap ' + str(num_boot) + ' **************************'
799 bootstrap_dir = boot_dir(num_boot)
807 makedirs([bootstrap_dir])
809 defaults = {'PORT': '1%d969' % num_boot,
810 'CACHE_DIR': bootstrap_dir,
812 'BOOTSTRAP': bootstrap_addresses,
813 'BOOTSTRAP_NODE': 'yes'}
816 defaults[k] = options[k]
817 f = open(join([bootstrap_dir, 'apt-p2p.conf']), 'w')
818 f.write(apt_p2p_conf_template % defaults)
821 pid = start('python', [join([sys.path[0], 'apt-p2p.py']),
822 '--config-file=' + join([bootstrap_dir, 'apt-p2p.conf']),
823 '--log-file=' + join([bootstrap_dir, 'apt-p2p.log']),],
828 def run_test(bootstraps, downloaders, apt_get_queue):
829 """Run a single test.
831 @type bootstraps: C{dictionary} of {C{int}: C{list} of C{string}}
832 @param bootstraps: the bootstrap nodes to start, keys are the bootstrap numbers and
833 values are the list of options to invoke the bootstrap node with
834 @type downloaders: C{dictionary} of {C{int}: (C{int}, C{list} of C{string})}
835 @param downloaders: the downloaders to start, keys are the downloader numbers and
836 values are the list of options to invoke the downloader with
837 @type apt_get_queue: C{list} of (C{int}, C{list} of C{string})
838 @param apt_get_queue: the apt-get downloader to use and commands to execute
839 @rtype: C{list} of (C{float}, C{int})
840 @return: the execution time and returned status code for each element of apt_get_queue
844 running_bootstraps = {}
845 running_downloaders = {}
850 boot_keys = bootstraps.keys()
852 bootstrap_addresses = bootstrap_address(boot_keys[0])
853 for i in xrange(1, len(boot_keys)):
854 bootstrap_addresses += '\n ' + bootstrap_address(boot_keys[i])
856 for k, v in bootstraps.items():
857 running_bootstraps[k] = start_bootstrap(bootstrap_addresses, k, **v)
861 for k, v in downloaders.items():
862 running_downloaders[k] = start_downloader(bootstrap_addresses, k, **v)
866 for (num_down, cmd) in apt_get_queue:
867 running_apt_get[num_down] = apt_get(num_down, cmd)
869 (pid, r_value) = os.waitpid(running_apt_get[num_down], 0)
870 elapsed = time() - start_time
871 del running_apt_get[num_down]
872 r_value = r_value / 256
873 apt_get_results.append((elapsed, r_value))
876 print '********** apt-get completed successfully in ' + str(elapsed) + ' sec. *****************'
878 print '********** apt-get finished with status ' + str(r_value) + ' in ' + str(elapsed) + ' sec. ************'
883 print '************************** Exception occurred **************************'
885 print '************************** will attempt to shut down *******************'
887 print '*********************** shutting down the apt-gets *******************'
888 for k, v in running_apt_get.items():
890 print 'apt-get', k, stop(v)
892 print '************************** Exception occurred **************************'
897 print '*********************** shutting down the downloaders *******************'
898 for k, v in running_downloaders.items():
900 print 'downloader', k, stop(v)
902 print '************************** Exception occurred **************************'
907 print '************************** shutting down the bootstraps *******************'
908 for k, v in running_bootstraps.items():
910 print 'bootstrap', k, stop(v)
912 print '************************** Exception occurred **************************'
915 print '************************** Test Results *******************'
917 for (num_down, cmd) in apt_get_queue:
919 s = str(num_down) + ': "apt-get ' + ' '.join(cmd) + '" '
920 if len(apt_get_results) > i:
921 (elapsed, r_value) = apt_get_results[i]
922 s += 'took ' + str(elapsed) + ' secs (' + str(r_value) + ')'
924 s += 'did not complete'
927 return apt_get_results
930 """Get the usage information to display to the user.
933 @return: the usage information to display
937 s = 'Usage: ' + sys.argv[0] + ' (all|<test>|help)\n\n'
938 s += ' all - run all the tests\n'
939 s += ' help - display this usage information\n'
940 s += ' <test> - run the <test> test (see list below for valid tests)\n\n'
945 s += 'test "' + str(k) + '" - ' + v[0] + '\n'
949 if __name__ == '__main__':
950 if len(sys.argv) != 2:
952 elif sys.argv[1] == 'all':
953 for k, v in tests.items():
954 run_test(v[1], v[2], v[3])
955 elif sys.argv[1] in tests:
956 v = tests[sys.argv[1]]
957 run_test(v[1], v[2], v[3])
958 elif sys.argv[1] == 'help':
961 print 'Unknown test to run:', sys.argv[1], '\n'