diff options
Diffstat (limited to 'mirrorselect/selectors.py')
-rw-r--r-- | mirrorselect/selectors.py | 1021 |
1 files changed, 551 insertions, 470 deletions
diff --git a/mirrorselect/selectors.py b/mirrorselect/selectors.py index 9647e56..4ec4474 100644 --- a/mirrorselect/selectors.py +++ b/mirrorselect/selectors.py @@ -37,6 +37,7 @@ import time import hashlib import urllib.request, urllib.parse, urllib.error + url_parse = urllib.parse.urlparse url_unparse = urllib.parse.urlunparse url_open = urllib.request.urlopen @@ -53,515 +54,595 @@ NETSELECT_SUPPORTS_IPV4_IPV6 = True class Shallow: - """handles rapid server selection via netselect""" - - def __init__(self, hosts, options, output): - self._options = options - self.output = output - self.urls = [] - - if options.blocksize is not None: - self.netselect_split(hosts, options.servers, - options.blocksize) - else: - self.netselect(hosts, options.servers) - - if len(self.urls) == 0: - self.output.print_err('Netselect failed to return any mirrors.' - ' Try again using block mode.') - + """handles rapid server selection via netselect""" - def netselect(self, hosts, number, quiet=False): - """ - Uses Netselect to choose the closest hosts, _very_ quickly - """ - if not quiet: - hosts = [host[0] for host in hosts] - top_host_dict = {} - top_hosts = [] + def __init__(self, hosts, options, output): + self._options = options + self.output = output + self.urls = [] - if not quiet: - self.output.print_info('Using netselect to choose the top ' - '%d mirrors...' % number) + if options.blocksize is not None: + self.netselect_split(hosts, options.servers, options.blocksize) + else: + self.netselect(hosts, options.servers) + + if len(self.urls) == 0: + self.output.print_err( + "Netselect failed to return any mirrors." " Try again using block mode." + ) - host_string = ' '.join(hosts) + def netselect(self, hosts, number, quiet=False): + """ + Uses Netselect to choose the closest hosts, _very_ quickly + """ + if not quiet: + hosts = [host[0] for host in hosts] + top_host_dict = {} + top_hosts = [] - cmd = ['netselect', '-s%d' % (number,)] + if not quiet: + self.output.print_info( + "Using netselect to choose the top " "%d mirrors..." % number + ) - if NETSELECT_SUPPORTS_IPV4_IPV6: - if self._options.ipv4: - cmd.append('-4') - elif self._options.ipv6: - cmd.append('-6') + host_string = " ".join(hosts) - cmd.extend(hosts) + cmd = ["netselect", "-s%d" % (number,)] - self.output.write('\nnetselect(): running "%s"\n' - % ' '.join(cmd), 2) + if NETSELECT_SUPPORTS_IPV4_IPV6: + if self._options.ipv4: + cmd.append("-4") + elif self._options.ipv6: + cmd.append("-6") + + cmd.extend(hosts) + + self.output.write('\nnetselect(): running "%s"\n' % " ".join(cmd), 2) + + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + out, err = proc.communicate() - proc = subprocess.Popen(cmd, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if err: + self.output.write("netselect(): netselect stderr: %s\n" % err, 2) - out, err = proc.communicate() + for line in out.splitlines(): + line = line.split() + if len(line) < 2: + continue + top_hosts.append(line[1]) + top_host_dict[line[0]] = line[1] - if err: - self.output.write('netselect(): netselect stderr: %s\n' % err, 2) + if not quiet: + self.output.write("Done.\n") - for line in out.splitlines(): - line = line.split() - if len(line) < 2: - continue - top_hosts.append(line[1]) - top_host_dict[line[0]] = line[1] + self.output.write( + "\nnetselect(): returning {} and {}\n".format(top_hosts, top_host_dict), 2 + ) - if not quiet: - self.output.write('Done.\n') + if quiet: + return top_hosts, top_host_dict + else: + self.urls = top_hosts - self.output.write('\nnetselect(): returning {} and {}\n'.format(top_hosts, - top_host_dict), 2) + def netselect_split(self, hosts, number, block_size): + """ + This uses netselect to test mirrors in chunks, + each at most block_size in length. + This is done in a tournament style. + """ + hosts = [host[0] for host in hosts] - if quiet: - return top_hosts, top_host_dict - else: - self.urls = top_hosts + self.output.write("netselect_split() got %s hosts.\n" % len(hosts), 2) + host_blocks = self.host_blocks(hosts, block_size) - def netselect_split(self, hosts, number, block_size): - """ - This uses netselect to test mirrors in chunks, - each at most block_size in length. - This is done in a tournament style. - """ - hosts = [host[0] for host in hosts] + self.output.write(" split into %s blocks\n" % len(host_blocks), 2) - self.output.write('netselect_split() got %s hosts.\n' % len(hosts), 2) + top_hosts = [] + ret_hosts = {} - host_blocks = self.host_blocks(hosts, block_size) + block_index = 0 + for block in host_blocks: + self.output.print_info( + "Using netselect to choose the top " + "%d hosts, in blocks of %s. %s of %s blocks complete." + % (number, block_size, block_index, len(host_blocks)) + ) - self.output.write(' split into %s blocks\n' % len(host_blocks), 2) + host_dict = self.netselect(block, len(block), quiet=True)[1] - top_hosts = [] - ret_hosts = {} + self.output.write( + "ran netselect(%s, %s), and got %s\n" % (block, len(block), host_dict), + 2, + ) - block_index = 0 - for block in host_blocks: - self.output.print_info('Using netselect to choose the top ' - '%d hosts, in blocks of %s. %s of %s blocks complete.' - % (number, block_size, block_index, len(host_blocks))) + for key in list(host_dict.keys()): + ret_hosts[key] = host_dict[key] + block_index += 1 - host_dict = self.netselect(block, len(block), quiet=True)[1] + sys.stderr.write( + "\rUsing netselect to choose the top" + "%d hosts, in blocks of %s. %s of %s blocks complete.\n" + % (number, block_size, block_index, len(host_blocks)) + ) - self.output.write('ran netselect(%s, %s), and got %s\n' - % (block, len(block), host_dict), 2) + host_ranking_keys = list(ret_hosts.keys()) + host_ranking_keys.sort() - for key in list(host_dict.keys()): - ret_hosts[key] = host_dict[key] - block_index += 1 + for rank in host_ranking_keys[:number]: + top_hosts.append(ret_hosts[rank]) - sys.stderr.write('\rUsing netselect to choose the top' - '%d hosts, in blocks of %s. %s of %s blocks complete.\n' - % (number, block_size, block_index, len(host_blocks))) + self.output.write("netselect_split(): returns %s\n" % top_hosts, 2) - host_ranking_keys = list(ret_hosts.keys()) - host_ranking_keys.sort() + self.urls = top_hosts - for rank in host_ranking_keys[:number]: - top_hosts.append(ret_hosts[rank]) + def host_blocks(self, hosts, block_size): + """ + Takes a list of hosts and a block size, + and returns an list of lists of URLs. + Each of the sublists is at most block_size in length. + """ + host_array = [] + mylist = [] - self.output.write('netselect_split(): returns %s\n' % top_hosts, 2) + while len(hosts) > block_size: + while len(mylist) < block_size: + mylist.append(hosts.pop()) + host_array.append(mylist) + mylist = [] + host_array.append(hosts) - self.urls = top_hosts + self.output.write( + "\n_host_blocks(): returns " + "%s blocks, each about %s in size\n" + % (len(host_array), len(host_array[0])), + 2, + ) - - def host_blocks(self, hosts, block_size): - """ - Takes a list of hosts and a block size, - and returns an list of lists of URLs. - Each of the sublists is at most block_size in length. - """ - host_array = [] - mylist = [] - - while len(hosts) > block_size: - while (len(mylist) < block_size): - mylist.append(hosts.pop()) - host_array.append(mylist) - mylist = [] - host_array.append(hosts) - - self.output.write('\n_host_blocks(): returns ' - '%s blocks, each about %s in size\n' - % (len(host_array), len(host_array[0])), 2) - - return host_array + return host_array class TimeoutException(Exception): - pass + pass def timeout_handler(signum, frame): - raise TimeoutException() + raise TimeoutException() class Deep: - """handles deep mode mirror selection.""" - - def __init__(self, hosts, options, output): - self.output = output - self.urls = [] - self._hosts = hosts - self._number = options.servers - self._dns_timeout = options.timeout - self._connect_timeout = options.timeout - self._download_timeout = options.timeout - self.test_file = options.file - self.test_md5 = options.md5 - - addr_families = [] - if options.ipv4: - addr_families.append(socket.AF_INET) - elif options.ipv6: - addr_families.append(socket.AF_INET6) - else: - addr_families.append(socket.AF_UNSPEC) - - self._addr_families = addr_families - - self.deeptest() - - def deeptest(self): - """ - Takes a list of hosts and returns the fastest, using _deeptime() - Doesn't waste time finnishing a test that has already taken longer than - the slowest mirror weve already got. - """ - top_hosts = {} - prog = 0 - maxtime = self._download_timeout - hosts = [host[0] for host in self._hosts] - num_hosts = len(hosts) - self.dl_failures = 0 - - for host in hosts: - - prog += 1 - if self.test_file != 'mirrorselect-test': - self.output.print_info( - 'Downloading %s files from each mirror... [%s of %s]' - % (self.test_file, prog, num_hosts) ) - else: - self.output.print_info( - 'Downloading 100k files from each mirror... [%s of %s]' - % (prog, num_hosts) ) - - mytime, ignore = self.deeptime(host, maxtime) - - if not ignore and mytime < maxtime: - maxtime, top_hosts = self._list_add((mytime, host), \ - maxtime, top_hosts, self._number) - else: - continue - - self.output.write('deeptest(): got %s hosts, and returned %s\n' - % (num_hosts, str(list(top_hosts.values()))), 2) - - self.output.write('\n') #this just makes output nicer - - #can't just return the dict.values, - #because we want the fastest mirror first... - keys = list(top_hosts.keys()) - keys.sort() - - rethosts = [] - for key in keys: - #self.output.write('deeptest(): adding rethost ' - #'%s, %s' % (key, top_hosts[key]), 2) - rethosts.append(top_hosts[key]) - - self.output.write('deeptest(): final rethost %s\n' % (rethosts), 2) - self.output.write('deeptest(): final md5 failures %s of %s\n' - % (self.dl_failures, num_hosts), 2) - self.urls = rethosts - - - def deeptime(self, url, maxtime): - """ - Takes a single url and fetch command, and downloads the test file. - Can be given an optional timeout, for use with a clever algorithm. - Like mine. - """ - self.output.write('\n_deeptime(): maxtime is %s\n' % maxtime, 2) - - if url.endswith('/'): #append the path to the testfile to the URL - url = url + 'distfiles/' + self.test_file - else: - url = url + '/distfiles/' + self.test_file - - url_parts = url_parse(url) - - signal.signal(signal.SIGALRM, timeout_handler) - - ips = [] - for addr_family in self._addr_families: - try: - try: - signal.alarm(self._dns_timeout) - for result in socket.getaddrinfo( - url_parts.hostname, None, addr_family, - socket.SOCK_STREAM, 0, socket.AI_ADDRCONFIG): - family, _, __, ___, sockaddr = result - ip = sockaddr[0] - if family == socket.AF_INET6: - ip = "[%s]" % ip - ips.append(ip) - finally: - signal.alarm(0) - except OSError as e: - self.output.write('deeptime(): dns error for host %s: %s\n' - % (url_parts.hostname, e), 2) - except TimeoutException: - self.output.write('deeptime(): dns timeout for host %s\n' - % url_parts.hostname, 2) - - if not ips: - self.output.write('deeptime(): unable to resolve ip for host %s\n' - % url_parts.hostname, 2) - return (None, True) - - self.output.write("deeptime(): ip's for host %s: %s\n" - % (url_parts.hostname, str(ips)), 2) - delta = 0 - f = None - - for ip in ips: - test_parts = url_parts._replace(netloc=ip) - test_url = url_unparse(test_parts) - self.output.write('deeptime(): testing url: %s\n' % test_url, 2) - - f, test_url, early_out = self._test_connection(test_url, url_parts, - ip, ips[ips.index(ip):]) - if early_out: - break - - if f is None: - self.output.write('deeptime(): unable to ' + \ - 'connect to host %s\n' % \ - (url_parts.hostname,), 2) - return (None, True) - - try: - # Close the initial "wake up" connection. - try: - signal.alarm(self._connect_timeout) - f.close() - finally: - signal.alarm(0) - except OSError as e: - self.output.write(('deeptime(): closing connection to host %s ' - 'failed for ip %s: %s\n') % (url_parts.hostname, ip, e), 2) - except TimeoutException: - self.output.write(('deeptime(): closing connection to host %s ' - 'timed out for ip %s\n') % (url_parts.hostname, ip), 2) - - self.output.write('deeptime(): timing url: %s\n' % test_url, 2) - try: - # The first connection serves to "wake up" the route between - # the local and remote machines. A second connection is used - # for the timed run. - try: - signal.alarm(int(math.ceil(maxtime))) - stime = time.time() - r = url_request(test_url) - r.host = url_parts.netloc - f = url_open(r) - - md5 = hashlib.md5(f.read()).hexdigest() - - delta = time.time() - stime - f.close() - if md5 != self.test_md5: - self.output.write( - "\ndeeptime(): md5sum error for file: %s\n" - % self.test_file + - " expected: %s\n" % self.test_md5 + - " got.....: %s\n" % md5 + - " host....: %s, %s\n" - % (url_parts.hostname, ip)) - self.dl_failures += 1 - return (None, True) - - finally: - signal.alarm(0) - - except (OSError, ssl.CertificateError) as e: - self.output.write(('\ndeeptime(): download from host %s ' - 'failed for ip %s: %s\n') % (url_parts.hostname, ip, e), 2) - return (None, True) - except TimeoutException: - self.output.write(('\ndeeptime(): download from host %s ' - 'timed out for ip %s\n') % (url_parts.hostname, ip), 2) - return (None, True) - except IncompleteRead as e: - self.output.write(('\ndeeptime(): download from host %s ' - 'failed for ip %s: %s\n') % (url_parts.hostname, ip, e), 2) - return (None, True) - - signal.signal(signal.SIGALRM, signal.SIG_DFL) - - self.output.write('deeptime(): download completed.\n', 2) - self.output.write('deeptime(): %s seconds for host %s\n' - % (delta, url), 2) - return (delta, False) - - - def _test_connection(self, test_url, url_parts, ip, ips): - """Tests the url for a connection, will recurse using - the original url instead of the ip if an HTTPError occurs - Returns f, test_url, early_out - """ - early_out = False - f = None - try: - try: - signal.alarm(self._connect_timeout) - r = url_request(test_url) - r.host = url_parts.netloc - f = url_open(r) - early_out = True - finally: - signal.alarm(0) - except HTTPError as e: - self.output.write('deeptime(): connection to host %s\n' - ' returned HTTPError: %s for ip %s\n' - ' Switching back to original url\n' - % (url_parts.hostname, e, ip), 2) - if len(ips) == 1: - test_url = url_unparse(url_parts) - return self._test_connection(test_url, url_parts, ip, []) - except (OSError, ssl.CertificateError) as e: - self.output.write('deeptime(): connection to host %s ' - 'failed for ip %s:\n %s\n' - % (url_parts.hostname, ip, e), 2) - except TimeoutException: - self.output.write(('deeptime(): connection to host %s ' - 'timed out for ip %s\n') % (url_parts.hostname, ip), 2) - except Exception as e: # Add general exception to catch any other errors - self.output.print_warn(('deeptime(): connection to host %s ' - 'errored for ip %s\n %s\n' - ' Please file a bug for this error at bugs.gentoo.org') - % (url_parts.hostname, ip, e), 0) - return f, test_url, early_out - - - def _list_add(self, time_host, maxtime, host_dict, maxlen): - """ - Takes argumets ((time, host), maxtime, host_dict, maxlen) - Adds a new time:host pair to the dictionary of top hosts. - If the dictionary is full, the slowest host is removed to make space. - Returns the new maxtime, be it the specified timeout, - or the slowest host. - """ - if len(host_dict) < maxlen: #still have room, and host is fast. add it. - - self.output.write('_list_add(): added host %s. with a time of %s\n' - % (time_host[1], time_host[0]), 2) - - host_dict.update(dict([time_host])) - times = list(host_dict.keys()) - times.sort() - - else: #We need to make room in the dict before we add. Kill the slowest. - self.output.write('_list_add(): Adding host %s with a time of %s\n' - % (time_host[1], time_host[0]), 2) - times = list(host_dict.keys()) - times.sort() - self.output.write('_list_add(): removing %s\n' - % host_dict[times[-1]], 2) - del host_dict[times[-1]] - host_dict.update(dict([time_host])) - #done adding. now return the appropriate time - times = list(host_dict.keys()) - times.sort() - - if len(host_dict) < maxlen: #check again to choose new timeout - self.output.write('_list_add(): host_dict is not full yet.' - ' reusing timeout of %s sec.\n' % maxtime, 2) - retval = maxtime - else: - self.output.write('_list_add(): host_dict is full. ' - 'Selecting the best timeout\n', 2) - if times[-1] < maxtime: - retval = times[-1] - else: - retval = maxtime - - self.output.write('_list_add(): new max time is %s seconds,' - ' and now len(host_dict)= %s\n' % (retval, len(host_dict)), 2) - - return retval, host_dict + """handles deep mode mirror selection.""" + + def __init__(self, hosts, options, output): + self.output = output + self.urls = [] + self._hosts = hosts + self._number = options.servers + self._dns_timeout = options.timeout + self._connect_timeout = options.timeout + self._download_timeout = options.timeout + self.test_file = options.file + self.test_md5 = options.md5 + + addr_families = [] + if options.ipv4: + addr_families.append(socket.AF_INET) + elif options.ipv6: + addr_families.append(socket.AF_INET6) + else: + addr_families.append(socket.AF_UNSPEC) + + self._addr_families = addr_families + + self.deeptest() + + def deeptest(self): + """ + Takes a list of hosts and returns the fastest, using _deeptime() + Doesn't waste time finnishing a test that has already taken longer than + the slowest mirror weve already got. + """ + top_hosts = {} + prog = 0 + maxtime = self._download_timeout + hosts = [host[0] for host in self._hosts] + num_hosts = len(hosts) + self.dl_failures = 0 + + for host in hosts: + + prog += 1 + if self.test_file != "mirrorselect-test": + self.output.print_info( + "Downloading %s files from each mirror... [%s of %s]" + % (self.test_file, prog, num_hosts) + ) + else: + self.output.print_info( + "Downloading 100k files from each mirror... [%s of %s]" + % (prog, num_hosts) + ) + + mytime, ignore = self.deeptime(host, maxtime) + + if not ignore and mytime < maxtime: + maxtime, top_hosts = self._list_add( + (mytime, host), maxtime, top_hosts, self._number + ) + else: + continue + + self.output.write( + "deeptest(): got %s hosts, and returned %s\n" + % (num_hosts, str(list(top_hosts.values()))), + 2, + ) + + self.output.write("\n") # this just makes output nicer + + # can't just return the dict.values, + # because we want the fastest mirror first... + keys = list(top_hosts.keys()) + keys.sort() + + rethosts = [] + for key in keys: + # self.output.write('deeptest(): adding rethost ' + #'%s, %s' % (key, top_hosts[key]), 2) + rethosts.append(top_hosts[key]) + + self.output.write("deeptest(): final rethost %s\n" % (rethosts), 2) + self.output.write( + "deeptest(): final md5 failures %s of %s\n" % (self.dl_failures, num_hosts), + 2, + ) + self.urls = rethosts + + def deeptime(self, url, maxtime): + """ + Takes a single url and fetch command, and downloads the test file. + Can be given an optional timeout, for use with a clever algorithm. + Like mine. + """ + self.output.write("\n_deeptime(): maxtime is %s\n" % maxtime, 2) + + if url.endswith("/"): # append the path to the testfile to the URL + url = url + "distfiles/" + self.test_file + else: + url = url + "/distfiles/" + self.test_file + + url_parts = url_parse(url) + + signal.signal(signal.SIGALRM, timeout_handler) + + ips = [] + for addr_family in self._addr_families: + try: + try: + signal.alarm(self._dns_timeout) + for result in socket.getaddrinfo( + url_parts.hostname, + None, + addr_family, + socket.SOCK_STREAM, + 0, + socket.AI_ADDRCONFIG, + ): + family, _, __, ___, sockaddr = result + ip = sockaddr[0] + if family == socket.AF_INET6: + ip = "[%s]" % ip + ips.append(ip) + finally: + signal.alarm(0) + except OSError as e: + self.output.write( + "deeptime(): dns error for host %s: %s\n" % (url_parts.hostname, e), + 2, + ) + except TimeoutException: + self.output.write( + "deeptime(): dns timeout for host %s\n" % url_parts.hostname, 2 + ) + + if not ips: + self.output.write( + "deeptime(): unable to resolve ip for host %s\n" % url_parts.hostname, 2 + ) + return (None, True) + + self.output.write( + "deeptime(): ip's for host %s: %s\n" % (url_parts.hostname, str(ips)), 2 + ) + delta = 0 + f = None + + for ip in ips: + test_parts = url_parts._replace(netloc=ip) + test_url = url_unparse(test_parts) + self.output.write("deeptime(): testing url: %s\n" % test_url, 2) + + f, test_url, early_out = self._test_connection( + test_url, url_parts, ip, ips[ips.index(ip) :] + ) + if early_out: + break + + if f is None: + self.output.write( + "deeptime(): unable to " + + "connect to host %s\n" % (url_parts.hostname,), + 2, + ) + return (None, True) + + try: + # Close the initial "wake up" connection. + try: + signal.alarm(self._connect_timeout) + f.close() + finally: + signal.alarm(0) + except OSError as e: + self.output.write( + ("deeptime(): closing connection to host %s " "failed for ip %s: %s\n") + % (url_parts.hostname, ip, e), + 2, + ) + except TimeoutException: + self.output.write( + ("deeptime(): closing connection to host %s " "timed out for ip %s\n") + % (url_parts.hostname, ip), + 2, + ) + + self.output.write("deeptime(): timing url: %s\n" % test_url, 2) + try: + # The first connection serves to "wake up" the route between + # the local and remote machines. A second connection is used + # for the timed run. + try: + signal.alarm(int(math.ceil(maxtime))) + stime = time.time() + r = url_request(test_url) + r.host = url_parts.netloc + f = url_open(r) + + md5 = hashlib.md5(f.read()).hexdigest() + + delta = time.time() - stime + f.close() + if md5 != self.test_md5: + self.output.write( + "\ndeeptime(): md5sum error for file: %s\n" % self.test_file + + " expected: %s\n" % self.test_md5 + + " got.....: %s\n" % md5 + + " host....: %s, %s\n" % (url_parts.hostname, ip) + ) + self.dl_failures += 1 + return (None, True) + + finally: + signal.alarm(0) + + except (OSError, ssl.CertificateError) as e: + self.output.write( + ("\ndeeptime(): download from host %s " "failed for ip %s: %s\n") + % (url_parts.hostname, ip, e), + 2, + ) + return (None, True) + except TimeoutException: + self.output.write( + ("\ndeeptime(): download from host %s " "timed out for ip %s\n") + % (url_parts.hostname, ip), + 2, + ) + return (None, True) + except IncompleteRead as e: + self.output.write( + ("\ndeeptime(): download from host %s " "failed for ip %s: %s\n") + % (url_parts.hostname, ip, e), + 2, + ) + return (None, True) + + signal.signal(signal.SIGALRM, signal.SIG_DFL) + + self.output.write("deeptime(): download completed.\n", 2) + self.output.write("deeptime(): %s seconds for host %s\n" % (delta, url), 2) + return (delta, False) + + def _test_connection(self, test_url, url_parts, ip, ips): + """Tests the url for a connection, will recurse using + the original url instead of the ip if an HTTPError occurs + Returns f, test_url, early_out + """ + early_out = False + f = None + try: + try: + signal.alarm(self._connect_timeout) + r = url_request(test_url) + r.host = url_parts.netloc + f = url_open(r) + early_out = True + finally: + signal.alarm(0) + except HTTPError as e: + self.output.write( + "deeptime(): connection to host %s\n" + " returned HTTPError: %s for ip %s\n" + " Switching back to original url\n" + % (url_parts.hostname, e, ip), + 2, + ) + if len(ips) == 1: + test_url = url_unparse(url_parts) + return self._test_connection(test_url, url_parts, ip, []) + except (OSError, ssl.CertificateError) as e: + self.output.write( + "deeptime(): connection to host %s " + "failed for ip %s:\n %s\n" % (url_parts.hostname, ip, e), + 2, + ) + except TimeoutException: + self.output.write( + ("deeptime(): connection to host %s " "timed out for ip %s\n") + % (url_parts.hostname, ip), + 2, + ) + except Exception as e: # Add general exception to catch any other errors + self.output.print_warn( + ( + "deeptime(): connection to host %s " + "errored for ip %s\n %s\n" + " Please file a bug for this error at bugs.gentoo.org" + ) + % (url_parts.hostname, ip, e), + 0, + ) + return f, test_url, early_out + + def _list_add(self, time_host, maxtime, host_dict, maxlen): + """ + Takes argumets ((time, host), maxtime, host_dict, maxlen) + Adds a new time:host pair to the dictionary of top hosts. + If the dictionary is full, the slowest host is removed to make space. + Returns the new maxtime, be it the specified timeout, + or the slowest host. + """ + if len(host_dict) < maxlen: # still have room, and host is fast. add it. + + self.output.write( + "_list_add(): added host %s. with a time of %s\n" + % (time_host[1], time_host[0]), + 2, + ) + + host_dict.update(dict([time_host])) + times = list(host_dict.keys()) + times.sort() + + else: # We need to make room in the dict before we add. Kill the slowest. + self.output.write( + "_list_add(): Adding host %s with a time of %s\n" + % (time_host[1], time_host[0]), + 2, + ) + times = list(host_dict.keys()) + times.sort() + self.output.write("_list_add(): removing %s\n" % host_dict[times[-1]], 2) + del host_dict[times[-1]] + host_dict.update(dict([time_host])) + # done adding. now return the appropriate time + times = list(host_dict.keys()) + times.sort() + + if len(host_dict) < maxlen: # check again to choose new timeout + self.output.write( + "_list_add(): host_dict is not full yet." + " reusing timeout of %s sec.\n" % maxtime, + 2, + ) + retval = maxtime + else: + self.output.write( + "_list_add(): host_dict is full. " "Selecting the best timeout\n", 2 + ) + if times[-1] < maxtime: + retval = times[-1] + else: + retval = maxtime + + self.output.write( + "_list_add(): new max time is %s seconds," + " and now len(host_dict)= %s\n" % (retval, len(host_dict)), + 2, + ) + + return retval, host_dict class Interactive: - """Handles interactive host selection.""" - - def __init__(self, hosts, options, output): - self.output = output - self.urls = [] - - self.interactive(hosts, options) - self.output.write('Interactive.interactive(): self.urls = %s\n' - % self.urls, 2) - - if not self.urls or len(self.urls[0]) == 0: - sys.exit(1) - - - def interactive(self, hosts, options): - """ - Some sort of interactive menu thingy. - """ - if options.rsync: - dialog = ['dialog', '--stdout', '--title', '"Gentoo RSYNC Mirrors"', - '--radiolist', '"Please select your desired mirror:"', - '20', '110', '14'] - else: - dialog = ['dialog', '--separate-output', '--stdout', '--title', - '"Gentoo Download Mirrors"', '--checklist', - '"Please select your desired mirrors:'] - if not options.ipv4 and not options.ipv6: - dialog[-1] += '\n* = supports ipv6' - - dialog.extend(['20', '110', '14']) - - for (url, args) in sorted(hosts, key = lambda x: - (x[1]['country'].lower(), x[1]['name'].lower()) ): - marker = "" - if options.rsync and not url.endswith("/gentoo-portage"): - url+="/gentoo-portage" - if (not options.ipv6 and not options.ipv4) and args['ipv6'] == 'y': - marker = "* " - if options.ipv6 and ( args['ipv6'] == 'n' ): continue - if options.ipv4 and ( args['ipv4'] == 'n' ): continue - - #dialog.append('"%s" "%s%s: %s" "OFF"' - #% ( url, marker, args['country'], args['name'])) - dialog.extend(["%s" %url, - "%s%s: %s" %(marker, args['country'], args['name']), - "OFF"]) - dialog = [encoder(x, get_encoding(sys.stdout)) for x in dialog] - proc = subprocess.Popen( dialog, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - out, err = proc.communicate() - - self.urls = out.splitlines() - - sys.stderr.write("\x1b[2J\x1b[H") - if self.urls: - if hasattr(self.urls[0], 'decode'): - self.urls = decode_selection( - [x.decode('utf-8').rstrip() for x in self.urls]) - else: - self.urls = decode_selection([x.rstrip() for x in self.urls]) - + """Handles interactive host selection.""" + + def __init__(self, hosts, options, output): + self.output = output + self.urls = [] + + self.interactive(hosts, options) + self.output.write("Interactive.interactive(): self.urls = %s\n" % self.urls, 2) + + if not self.urls or len(self.urls[0]) == 0: + sys.exit(1) + + def interactive(self, hosts, options): + """ + Some sort of interactive menu thingy. + """ + if options.rsync: + dialog = [ + "dialog", + "--stdout", + "--title", + '"Gentoo RSYNC Mirrors"', + "--radiolist", + '"Please select your desired mirror:"', + "20", + "110", + "14", + ] + else: + dialog = [ + "dialog", + "--separate-output", + "--stdout", + "--title", + '"Gentoo Download Mirrors"', + "--checklist", + '"Please select your desired mirrors:', + ] + if not options.ipv4 and not options.ipv6: + dialog[-1] += "\n* = supports ipv6" + + dialog.extend(["20", "110", "14"]) + + for (url, args) in sorted( + hosts, key=lambda x: (x[1]["country"].lower(), x[1]["name"].lower()) + ): + marker = "" + if options.rsync and not url.endswith("/gentoo-portage"): + url += "/gentoo-portage" + if (not options.ipv6 and not options.ipv4) and args["ipv6"] == "y": + marker = "* " + if options.ipv6 and (args["ipv6"] == "n"): + continue + if options.ipv4 and (args["ipv4"] == "n"): + continue + + # dialog.append('"%s" "%s%s: %s" "OFF"' + #% ( url, marker, args['country'], args['name'])) + dialog.extend( + [ + "%s" % url, + "%s%s: %s" % (marker, args["country"], args["name"]), + "OFF", + ] + ) + dialog = [encoder(x, get_encoding(sys.stdout)) for x in dialog] + proc = subprocess.Popen(dialog, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + out, err = proc.communicate() + + self.urls = out.splitlines() + + sys.stderr.write("\x1b[2J\x1b[H") + if self.urls: + if hasattr(self.urls[0], "decode"): + self.urls = decode_selection( + [x.decode("utf-8").rstrip() for x in self.urls] + ) + else: + self.urls = decode_selection([x.rstrip() for x in self.urls]) |