Completed
Push — master ( 2d0d7d...271e1f )
by Ryan
01:51 queued 50s
created

traceroutedb.run_runner()   D

Complexity

Conditions 10

Size

Total Lines 62

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 10
dl 0
loc 62
rs 4.6753

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like traceroutedb.run_runner() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#!/usr/bin/env python
2
3
from __future__ import print_function
4
import sys
5
import json
6
import socket
7
import time
8
import tracerouteparser
9
import signal
10
from requests import post, get, ConnectionError
11
from subprocess import check_output, Popen, PIPE
12
from multiprocessing import Pool
13
import logging
14
15
16
ON_POSIX = 'posix' in sys.builtin_module_names
17
18
19
def init_worker():
20
    signal.signal(signal.SIGINT, signal.SIG_IGN)
21
22
23
def own_ips():
24
    return check_output(["ip", "-4", "addr", "list"])
25
26
27
global OWN_IPS
28
OWN_IPS = own_ips()
29
30
31
def ext_ip():
32
    """
33
    We make the assumtion that we ony have one gateway to any one destination,
34
    could be better to use "ip route get $ip" but that won't cover NAT scenario
35
    """
36
    try:
37
        resp = get('https://httpbin.org/ip')
38
        return resp.json()["origin"]
39
    except ConnectionError as e:
40
        logging.warning("Could not get external ip:\n" + str(e))
41
        return None
42
43
44
def submit_trace(ip_dict, result):
45
    if result is None:
46
        return
47
    if ip_dict.get("simulate"):
48
        print(json.dumps(result))
49
    else:
50
        try:
51
            r = post(ip_dict["url"], data=json.dumps(result))
52
            logging.debug(r)
53
        except ConnectionError as e:
54
            print(e)
55
56
57
def run_trace(ip_dict):
58
    ip = ip_dict["ip"]
59
    if ip in OWN_IPS:
60
        return None
61
    try:
62
        proc = Popen(["/usr/sbin/traceroute", ip, "30"], stdout=PIPE, stderr=PIPE)
63
        out, err = proc.communicate()
64
    except KeyboardInterrupt:
65
        proc.terminate()
66
        proc.kill()
67
        return None
68
    src_ip = check_output(["ip", "route", "get", ip]).splitlines()[0].split()[-1]
69
    trp = tracerouteparser.TracerouteParser()
70
    trp.parse_data(out)
71
    data = {}
72
    data["dst_ip"] = trp.dest_ip
73
    data["src_ip"] = src_ip
74
    data["dst_name"] = trp.dest_name
75
    data["hops"] = {}
76
77
    for hop in trp.hops:
78
        data["hops"][int(hop.idx)] = []
79
        for probe in hop.probes:
80
            data["hops"][int(hop.idx)].append({"name": probe.name,
81
                                               "ip": probe.ipaddr,
82
                                               "rtt": probe.rtt,
83
                                               "anno": probe.anno})
84
    ret = {"reporter": socket.gethostname(),
85
           "note": ip_dict["note"],
86
           "ext_ip": ip_dict["ext_ip"],
87
           "data": data}
88
    submit_trace(ip_dict, ret)
89
90
91
def run_runner(config):
92
    NUMPROCS = config.procs
93
94
    if config.debug:
95
        logging.basicConfig(level=logging.DEBUG)
96
        logging.getLogger("requests").setLevel(logging.DEBUG)
97
    else:
98
        logging.basicConfig(level=logging.WARNING)
99
        logging.getLogger("requests").setLevel(logging.WARNING)
100
    start_time = time.time()
101
102
    logging.info("Traceroute runner starting")
103
104
    if config.get("server_url", False):
105
        URL = config.server_url + "/trace"
106
    else:
107
        URL = "http://127.0.0.1:9001" + "/trace"
108
109
    if config.get("ips", False):
110
        ips = config.ips
111
        if config.get("ips_file", False):
112
            logging.warning("-i overrides ips from file with -f")
113
    elif config.ips_file:
114
        ips = []
115
        with open(config.ips_file) as f:
116
            for line in f:
117
                ips.append(line.strip())
118
    else:
119
        logging.error("No ips defined, exiting")
120
        sys.exit(1)
121
122
# The only reason we need to pack all this info into a list is that
123
# we only get to pass one iterable to function from map_async or
124
# thats how I (poorly) understand it
125
    ips_to_iter = []
126
    detected_ext_ip = ext_ip()
127
    for ip in ips:
128
        ip_dict = {}
129
        ip = str(ip)
130
        ip_dict["note"] = config.get("note", None)
131
        ip_dict["ext_ip"] = detected_ext_ip
132
        ip_dict["url"] = URL
133
        ip_dict["ip"] = ip
134
        ip_dict["simulate"] = config.get("simulate", None)
135
        ips_to_iter.append(ip_dict)
136
137
    logging.debug('IP addresses: ' + str(ips))
138
    logging.debug(str(ips_to_iter))
139
140
    try:
141
        pool = Pool(NUMPROCS, init_worker)
142
        pool.map_async(run_trace, ips_to_iter, 1).get(9999999)
143
        pool.close()
144
        pool.join()
145
    except KeyboardInterrupt:
146
        print("Caught KeyboardInterrupt, terminating workers")
147
        pool.terminate()
148
        pool.join()
149
        sys.exit(1)
150
151
    end_time = time.time()
152
    logging.info("Traceroute runner done, Took: " + str(int(end_time - start_time)) + " seconds")
153