OLD | NEW |
1 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 1 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import common, fnmatch, logging, os, re, string, threading, time | 5 import common, fnmatch, logging, os, re, string, threading, time |
6 | 6 |
7 from autotest_lib.server import autotest, hosts, subcommand | 7 from autotest_lib.server import autotest, hosts, subcommand |
8 from autotest_lib.server import site_bsd_router | 8 from autotest_lib.server import site_bsd_router |
9 from autotest_lib.server import site_linux_router | 9 from autotest_lib.server import site_linux_router |
10 from autotest_lib.server import site_host_attributes | 10 from autotest_lib.server import site_host_attributes |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
112 # The server machine may be multi-homed or only on the wifi | 112 # The server machine may be multi-homed or only on the wifi |
113 # network. When only on the wifi net we suppress server_* | 113 # network. When only on the wifi net we suppress server_* |
114 # requests since we cannot initiate them from the control machine. | 114 # requests since we cannot initiate them from the control machine. |
115 # | 115 # |
116 server = config['server'] | 116 server = config['server'] |
117 # NB: server may not be reachable on the control network | 117 # NB: server may not be reachable on the control network |
118 if 'addr' in server: | 118 if 'addr' in server: |
119 self.server = hosts.create_host(server['addr']) | 119 self.server = hosts.create_host(server['addr']) |
120 self.server_at = autotest.Autotest(self.server) | 120 self.server_at = autotest.Autotest(self.server) |
121 # if not specified assume the same as the control address | 121 # if not specified assume the same as the control address |
122 self.server_wifi_ip = getattr(server, 'wifi_addr', self.server.ip) | 122 self.server_wifi_ip = server.get('wifi_addr', self.server.ip) |
| 123 self.__server_discover_commands(server) |
123 else: | 124 else: |
124 self.server = None; | 125 self.server = None; |
125 # NB: wifi address must be set if not reachable from control | 126 # NB: wifi address must be set if not reachable from control |
126 self.server_wifi_ip = server['wifi_addr'] | 127 self.server_wifi_ip = server['wifi_addr'] |
127 | 128 |
128 # potential bg thread for ping untilstop | 129 # potential bg thread for ping untilstop |
129 self.ping_thread = None | 130 self.ping_thread = None |
130 | 131 |
131 # potential bg thread for client network monitoring | 132 # potential bg thread for client network monitoring |
132 self.client_netdump_thread = None | 133 self.client_netdump_thread = None |
133 self.client_cmd_netdump = client.get('cmd_netdump', 'tshark') | 134 self.__client_discover_commands(client) |
134 self.client_cmd_ifconfig = client.get('cmd_ifconfig', 'ifconfig') | 135 self.netperf_iter = 0 |
135 self.client_cmd_iw = client.get('cmd_iw', 'iw') | 136 self.firewall_rules = [] |
136 | 137 |
137 | 138 |
138 def cleanup(self, params): | 139 def cleanup(self, params): |
139 """ Cleanup state: disconnect client and destroy ap """ | 140 """ Cleanup state: disconnect client and destroy ap """ |
140 self.disconnect({}) | 141 self.disconnect({}) |
141 self.wifi.destroy({}) | 142 self.wifi.destroy({}) |
142 self.client_netdump_stop({}) | 143 self.client_netdump_stop({}) |
| 144 self.firewall_cleanup({}) |
| 145 |
| 146 |
| 147 def __client_discover_commands(self, client): |
| 148 self.client_cmd_netdump = client.get('cmd_netdump', 'tshark') |
| 149 self.client_cmd_ifconfig = client.get('cmd_ifconfig', 'ifconfig') |
| 150 self.client_cmd_iw = client.get('cmd_iw', 'iw') |
| 151 self.client_cmd_netperf = client.get('cmd_netperf_client', |
| 152 '/usr/local/bin/netperf') |
| 153 self.client_cmd_netserv = client.get('cmd_netperf_server', |
| 154 '/usr/local/sbin/netserver') |
| 155 self.client_cmd_iptables = '/sbin/iptables' |
| 156 |
| 157 |
| 158 def __server_discover_commands(self, server): |
| 159 self.server_cmd_netperf = server.get('cmd_netperf_client', |
| 160 '/usr/bin/netperf') |
| 161 self.server_cmd_netserv = server.get('cmd_netperf_server', |
| 162 '/usr/bin/netserver') |
| 163 # /usr/bin/ping is preferred, as it is likely to be iputils |
| 164 if self.__is_installed(self.server, '/usr/bin/ping'): |
| 165 self.server_ping_cmd = '/usr/bin/ping' |
| 166 else: |
| 167 self.server_ping_cmd = 'ping' |
143 | 168 |
144 | 169 |
145 def __get_defssid(self, ipaddr): | 170 def __get_defssid(self, ipaddr): |
146 # | 171 # |
147 # Calculate ssid based on test name; this lets us track progress | 172 # Calculate ssid based on test name; this lets us track progress |
148 # by watching beacon frames. | 173 # by watching beacon frames. |
149 # | 174 # |
150 return re.sub('[^a-zA-Z0-9_]', '_', "%s_%s" % (self.name, ipaddr)) | 175 return re.sub('[^a-zA-Z0-9_]', '_', "%s_%s" % (self.name, ipaddr)) |
151 | 176 |
152 | 177 |
(...skipping 224 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
377 def __get_pingstats(self, str): | 402 def __get_pingstats(self, str): |
378 stats = {} | 403 stats = {} |
379 for k in ('xmit', 'recv', 'loss', 'min', 'avg', 'max'): | 404 for k in ('xmit', 'recv', 'loss', 'min', 'avg', 'max'): |
380 stats[k] = '???' | 405 stats[k] = '???' |
381 m = re.search('([0-9]*) packets transmitted,[ ]*([0-9]*)[ ]' | 406 m = re.search('([0-9]*) packets transmitted,[ ]*([0-9]*)[ ]' |
382 '(packets |)received, ([0-9]*)', str) | 407 '(packets |)received, ([0-9]*)', str) |
383 if m is not None: | 408 if m is not None: |
384 stats['xmit'] = m.group(1) | 409 stats['xmit'] = m.group(1) |
385 stats['recv'] = m.group(2) | 410 stats['recv'] = m.group(2) |
386 stats['loss'] = m.group(4) | 411 stats['loss'] = m.group(4) |
387 m = re.search('(round-trip|rtt) min[^=]*= ([0-9.]*)/([0-9.]*)/([0-9.]*)'
, str) | 412 m = re.search('(round-trip|rtt) min[^=]*= ' |
| 413 '([0-9.]*)/([0-9.]*)/([0-9.]*)', str) |
388 if m is not None: | 414 if m is not None: |
389 stats['min'] = m.group(2) | 415 stats['min'] = m.group(2) |
390 stats['avg'] = m.group(3) | 416 stats['avg'] = m.group(3) |
391 stats['max'] = m.group(4) | 417 stats['max'] = m.group(4) |
392 return stats | 418 return stats |
393 | 419 |
394 | 420 |
395 def __print_pingstats(self, label, stats): | 421 def __print_pingstats(self, label, stats): |
396 logging.info("%s: %s%s/%s, %s%% loss, rtt %s/%s/%s", | 422 logging.info("%s: %s%s/%s, %s%% loss, rtt %s/%s/%s", |
397 self.name, label, stats['xmit'], stats['recv'], stats['loss'], | 423 self.name, label, stats['xmit'], stats['recv'], stats['loss'], |
398 stats['min'], stats['avg'], stats['max']) | 424 stats['min'], stats['avg'], stats['max']) |
399 | 425 |
400 | 426 |
| 427 def __ping_prefix(self, params): |
| 428 if 'name' in params: |
| 429 return params['name'] |
| 430 |
| 431 args = [] |
| 432 for k, v in params.items(): |
| 433 if k != 'count': |
| 434 args.append('%s_%s' % (k, v)) |
| 435 return '/'.join(args) |
| 436 |
| 437 |
401 def client_ping(self, params): | 438 def client_ping(self, params): |
402 """ Ping the server from the client """ | 439 """ Ping the server from the client """ |
403 ping_ip = params.get('ping_ip', self.server_wifi_ip) | 440 ping_ip = params.get('ping_ip', self.server_wifi_ip) |
404 count = params.get('count', self.defpingcount) | 441 count = params.get('count', self.defpingcount) |
405 # set timeout for 3s / ping packet | 442 # set timeout for 3s / ping packet |
406 result = self.client.run("ping %s %s" % \ | 443 result = self.client.run("ping %s %s" % \ |
407 (self.__ping_args(params), ping_ip), timeout=3*int(count)) | 444 (self.__ping_args(params), ping_ip), timeout=3*int(count)) |
408 | 445 |
409 stats = self.__get_pingstats(result.stdout) | 446 stats = self.__get_pingstats(result.stdout) |
| 447 prefix = 'client_ping_%s_' % self.__ping_prefix(params) |
410 for k,v in stats.iteritems(): | 448 for k,v in stats.iteritems(): |
411 self.keyvals['client_ping_' + k] = v | 449 self.keyvals[prefix + k] = v |
412 self.__print_pingstats("client_ping ", stats) | 450 self.__print_pingstats("client_ping ", stats) |
413 | 451 |
414 | 452 |
415 def client_ping_bg(self, params): | 453 def client_ping_bg(self, params): |
416 """ Ping the server from the client """ | 454 """ Ping the server from the client """ |
417 ping_ip = params.get('ping_ip', self.server_wifi_ip) | 455 ping_ip = params.get('ping_ip', self.server_wifi_ip) |
418 cmd = "ping %s %s" % (self.__ping_args(params), ping_ip) | 456 cmd = "ping %s %s" % (self.__ping_args(params), ping_ip) |
419 self.ping_thread = HelperThread(self.client, cmd) | 457 self.ping_thread = HelperThread(self.client, cmd) |
420 self.ping_thread.start() | 458 self.ping_thread.start() |
421 | 459 |
422 | 460 |
423 def client_ping_bg_stop(self, params): | 461 def client_ping_bg_stop(self, params): |
424 if self.ping_thread is not None: | 462 if self.ping_thread is not None: |
425 self.client.run("pkill ping") | 463 self.client.run("pkill ping") |
426 self.ping_thread.join() | 464 self.ping_thread.join() |
427 self.ping_thread = None | 465 self.ping_thread = None |
428 | 466 |
429 | 467 |
430 def server_ping(self, params): | 468 def server_ping(self, params): |
431 """ Ping the client from the server """ | 469 """ Ping the client from the server """ |
432 if self.server is None: | 470 if self.server is None: |
433 self.__unreachable("server_ping") | 471 self.__unreachable("server_ping") |
434 return | 472 return |
435 ping_ip = params.get('ping_ip', self.client_wifi_ip) | 473 ping_ip = params.get('ping_ip', self.client_wifi_ip) |
436 count = params.get('count', self.defpingcount) | 474 count = params.get('count', self.defpingcount) |
437 # set timeout for 3s / ping packet | 475 # set timeout for 3s / ping packet |
438 result = self.server.run("ping %s %s" % \ | 476 result = self.server.run("%s %s %s" % \ |
439 (self.__ping_args(params), ping_ip), timeout=3*int(count)) | 477 (self.server_ping_cmd, self.__ping_args(params), |
| 478 ping_ip), timeout=3*int(count)) |
440 | 479 |
441 stats = self.__get_pingstats(result.stdout) | 480 stats = self.__get_pingstats(result.stdout) |
| 481 prefix = 'server_ping_' + self.__ping_prefix(params) |
442 for k,v in stats.iteritems(): | 482 for k,v in stats.iteritems(): |
443 self.keyvals['server_ping_' + k] = v | 483 self.keyvals['server_ping_' + k] = v |
444 self.__print_pingstats("server_ping ", stats) | 484 self.__print_pingstats("server_ping ", stats) |
445 | 485 |
446 | 486 |
447 def server_ping_bg(self, params): | 487 def server_ping_bg(self, params): |
448 """ Ping the client from the server """ | 488 """ Ping the client from the server """ |
449 if self.server is None: | 489 if self.server is None: |
450 self.__unreachable("server_ping_bg") | 490 self.__unreachable("server_ping_bg") |
451 return | 491 return |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
513 | 553 |
514 | 554 |
515 def server_iperf(self, params): | 555 def server_iperf(self, params): |
516 """ Run iperf on the server against the client """ | 556 """ Run iperf on the server against the client """ |
517 if self.server is None: | 557 if self.server is None: |
518 self.__unreachable("server_iperf") | 558 self.__unreachable("server_iperf") |
519 return | 559 return |
520 self.__run_iperf(self.server_wifi_ip, self.client_wifi_ip, params) | 560 self.__run_iperf(self.server_wifi_ip, self.client_wifi_ip, params) |
521 | 561 |
522 | 562 |
523 def __run_netperf(self, client_ip, server_ip, params): | 563 def __is_installed(self, host, filename): |
524 template = "job.run_test('" | 564 result = host.run("ls %s" % filename, ignore_status=True) |
525 if self.server is None: | 565 m = re.search(filename, result.stdout) |
526 template += "network_netperf2" | 566 return m is not None |
| 567 |
| 568 |
| 569 def __firewall_open(self, proto, src): |
| 570 rule = 'INPUT -s %s/32 -p %s -m %s -j ACCEPT' % (src, proto, proto) |
| 571 result = self.client.run('%s -S INPUT' % self.client_cmd_iptables) |
| 572 if '-A %s ' % rule in result.stdout.splitlines(): |
| 573 return None |
| 574 self.client.run('%s -A %s' % (self.client_cmd_iptables, rule)) |
| 575 self.firewall_rules.append(rule) |
| 576 return rule |
| 577 |
| 578 |
| 579 def __firewall_close(self, rule): |
| 580 if rule in self.firewall_rules: |
| 581 self.client.run('%s -D %s' % (self.client_cmd_iptables, rule)) |
| 582 self.firewall_rules.remove(rule) |
| 583 |
| 584 def firewall_cleanup(self, params): |
| 585 for rule in self.firewall_rules: |
| 586 self.__firewall_close(rule) |
| 587 |
| 588 def __run_netperf(self, mode, params): |
| 589 np_rules = [] |
| 590 if mode == 'server': |
| 591 server = { 'host': self.client, 'cmd': self.client_cmd_netserv } |
| 592 client = { 'host': self.server, 'cmd': self.server_cmd_netperf, |
| 593 'target': self.client_wifi_ip } |
| 594 |
| 595 # Open up access from the server into our DUT |
| 596 np_rules.append(self.__firewall_open('tcp', self.server_wifi_ip)) |
| 597 np_rules.append(self.__firewall_open('udp', self.server_wifi_ip)) |
527 else: | 598 else: |
528 template += "netperf2" | 599 server = { 'host': self.server, 'cmd': self.server_cmd_netserv } |
529 template += "', server_ip='%s', client_ip='%s', role='%s'" | 600 client = { 'host': self.client, 'cmd': self.client_cmd_netperf, |
530 if 'test' in params: | 601 'target': self.server_wifi_ip } |
531 template += ", test='%s'" % params['test'] | |
532 if 'bidir' in params: | |
533 template += ", bidi=True" | |
534 if 'time' in params: | |
535 template += ", test_time=%s" % params['time'] | |
536 template += ", wait_time=%s" % params.get('wait_time', self.defwaittime) | |
537 | 602 |
538 # add a tag to distinguish runs when multiple tests are run | 603 # If appropriate apps are not installed, raise an error |
539 if 'tag' in params: | 604 if not self.__is_installed(client['host'], client['cmd']) or \ |
540 template += ", tag='%s'" % params['tag'] | 605 not self.__is_installed(server['host'], server['cmd']): |
541 elif 'test' in params: | 606 raise error.TestFail('Unable to find netperf on client or server') |
542 template += ", tag='%s'" % params['test'] | |
543 | 607 |
544 template += ")" | 608 # There are legitimate ways this command can fail, eg. already running |
| 609 server['host'].run(server['cmd'], ignore_status=True) |
545 | 610 |
546 client_control_file = template % (server_ip, client_ip, 'client') | 611 # Assemble arguments for client command |
547 client_command = subcommand.subcommand(self.client_at.run, | 612 test = params.get('test', 'TCP_STREAM') |
548 [client_control_file, self.client.hostname]) | 613 netperf_args = '-H %s -t %s -l %d' % (client['target'], test, |
549 cmds = [client_command] | 614 params.get('test_time', 15)) |
550 | 615 |
551 if self.server is None: | 616 # Run netperf command and receive command results |
552 logging.info("%s: netperf %s => (%s)", | 617 t0 = time.time() |
553 self.name, client_ip, server_ip) | 618 results = client['host'].run("%s %s" % (client['cmd'], netperf_args)) |
| 619 actual_time = time.time() - t0 |
| 620 logging.info('actual_time: %f', actual_time) |
| 621 |
| 622 # Close up whatever firewall rules we created for netperf |
| 623 for rule in np_rules: |
| 624 self.__firewall_close(rule) |
| 625 |
| 626 # Results are prefixed with the iteration or a caller-defined name |
| 627 prefix = 'Netperf_%s_' % params.get('name', str(self.netperf_iter)) |
| 628 self.netperf_iter += 1 |
| 629 |
| 630 self.keyvals[prefix + 'test'] = test |
| 631 self.keyvals[prefix + 'mode'] = mode |
| 632 self.keyvals[prefix + 'actual_time'] = actual_time |
| 633 |
| 634 logging.info(results) |
| 635 |
| 636 lines = results.stdout.splitlines() |
| 637 |
| 638 # Each test type has a different form of output |
| 639 if test in ['TCP_STREAM', 'TCP_MAERTS', 'TCP_SENDFILE']: |
| 640 """Parses the following (works for both TCP_STREAM, TCP_MAERTS and |
| 641 TCP_SENDFILE) and returns a singleton containing throughput. |
| 642 |
| 643 TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to \ |
| 644 foo.bar.com (10.10.10.3) port 0 AF_INET |
| 645 Recv Send Send |
| 646 Socket Socket Message Elapsed |
| 647 Size Size Size Time Throughput |
| 648 bytes bytes bytes secs. 10^6bits/sec |
| 649 |
| 650 87380 16384 16384 2.00 941.28 |
| 651 """ |
| 652 self.keyvals[prefix + 'Throughput'] = float(lines[6].split()[4]) |
| 653 elif test == 'UDP_STREAM': |
| 654 """Parses the following and returns a touple containing throughput |
| 655 and the number of errors. |
| 656 |
| 657 UDP UNIDIRECTIONAL SEND TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET \ |
| 658 to foo.bar.com (10.10.10.3) port 0 AF_INET |
| 659 Socket Message Elapsed Messages |
| 660 Size Size Time Okay Errors Throughput |
| 661 bytes bytes secs # # 10^6bits/sec |
| 662 |
| 663 129024 65507 2.00 3673 0 961.87 |
| 664 131072 2.00 3673 961.87 |
| 665 """ |
| 666 udp_tokens = lines[5].split() |
| 667 self.keyvals[prefix + 'Throughput'] = float(udp_tokens[5]) |
| 668 self.keyvals[prefix + 'Errors'] = float(udp_tokens[4]) |
| 669 elif test in ['TCP_RR', 'TCP_CRR', 'UDP_RR']: |
| 670 """Parses the following which works for both rr (TCP and UDP) |
| 671 and crr tests and returns a singleton containing transfer rate. |
| 672 |
| 673 TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET \ |
| 674 to foo.bar.com (10.10.10.3) port 0 AF_INET |
| 675 Local /Remote |
| 676 Socket Size Request Resp. Elapsed Trans. |
| 677 Send Recv Size Size Time Rate |
| 678 bytes Bytes bytes bytes secs. per sec |
| 679 |
| 680 16384 87380 1 1 2.00 14118.53 |
| 681 16384 87380 |
| 682 """ |
| 683 self.kevals[prefix + 'Trasnfer_Rate'] = float(lines[6].split()[5]) |
554 else: | 684 else: |
555 server_control_file = template % (server_ip, client_ip, 'server') | 685 raise error.TestError('Unhandled test') |
556 server_command = subcommand.subcommand(self.server_at.run, | |
557 [server_control_file, self.server.hostname]) | |
558 cmds.append(server_command) | |
559 | 686 |
560 logging.info("%s: netperf %s => %s", | 687 return True |
561 self.name, client_ip, server_ip) | |
562 | |
563 subcommand.parallel(cmds) | |
564 | 688 |
565 | 689 |
566 def client_netperf(self, params): | 690 def client_netperf(self, params): |
567 """ Run netperf on the client against the server """ | 691 """ Run netperf on the client against the server """ |
568 self.__run_netperf(self.client_wifi_ip, self.server_wifi_ip, params) | 692 self.__run_netperf('client', params) |
| 693 |
569 | 694 |
570 def server_netperf(self, params): | 695 def server_netperf(self, params): |
571 """ Run netperf on the server against the client """ | 696 """ Run netperf on the server against the client """ |
572 if self.server is None: | 697 if self.server is None: |
573 self.__unreachable("server_netperf") | 698 self.__unreachable("server_netperf") |
574 return | 699 return |
575 self.__run_netperf(self.server_wifi_ip, self.client_wifi_ip, params) | 700 self.__run_netperf('server', params) |
576 | 701 |
577 | 702 |
578 def __create_netdump_dev(self, devname='mon0'): | 703 def __create_netdump_dev(self, devname='mon0'): |
579 self.client.run("%s dev %s del || /bin/true" % (self.client_cmd_iw, | 704 self.client.run("%s dev %s del || /bin/true" % (self.client_cmd_iw, |
580 devname)) | 705 devname)) |
581 self.client.run("%s dev %s interface add %s type monitor" % | 706 self.client.run("%s dev %s interface add %s type monitor" % |
582 (self.client_cmd_iw, self.client_wlanif, devname)) | 707 (self.client_cmd_iw, self.client_wlanif, devname)) |
583 self.client.run("%s %s up" % (self.client_cmd_ifconfig, devname)) | 708 self.client.run("%s %s up" % (self.client_cmd_ifconfig, devname)) |
584 return devname | 709 return devname |
585 | 710 |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
625 | 750 |
626 | 751 |
627 def __byfile(a, b): | 752 def __byfile(a, b): |
628 if a['file'] < b['file']: | 753 if a['file'] < b['file']: |
629 return -1 | 754 return -1 |
630 elif a['file'] > b['file']: | 755 elif a['file'] > b['file']: |
631 return 1 | 756 return 1 |
632 else: | 757 else: |
633 return 0 | 758 return 0 |
634 | 759 |
| 760 |
635 def read_tests(dir, *args): | 761 def read_tests(dir, *args): |
636 """ | 762 """ |
637 Collect WiFi test tuples from files. File names are used to | 763 Collect WiFi test tuples from files. File names are used to |
638 sort the test objects so the convention is to name them NNN<test> | 764 sort the test objects so the convention is to name them NNN<test> |
639 where NNN is a decimal number used to sort and <test> is an | 765 where NNN is a decimal number used to sort and <test> is an |
640 identifying name for the test; e.g. 000Check11b | 766 identifying name for the test; e.g. 000Check11b |
641 """ | 767 """ |
642 tests = [] | 768 tests = [] |
643 for file in os.listdir(dir): | 769 for file in os.listdir(dir): |
644 if any(fnmatch.fnmatch(file, pat) for pat in args): | 770 if any(fnmatch.fnmatch(file, pat) for pat in args): |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
679 if server_addr is None and hasattr(client_attributes, 'server_addr'): | 805 if server_addr is None and hasattr(client_attributes, 'server_addr'): |
680 server_addr = client_attributes.server_addr | 806 server_addr = client_attributes.server_addr |
681 if server_addr is not None: | 807 if server_addr is not None: |
682 server['addr'] = server_addr; | 808 server['addr'] = server_addr; |
683 # TODO(sleffler) check for wifi_addr when no control address | 809 # TODO(sleffler) check for wifi_addr when no control address |
684 | 810 |
685 # tag jobs w/ the router's address on the control network | 811 # tag jobs w/ the router's address on the control network |
686 config['tagname'] = router['addr'] | 812 config['tagname'] = router['addr'] |
687 | 813 |
688 return config | 814 return config |
OLD | NEW |