Completed
Pull Request — master (#634)
by
unknown
02:18
created

NapalmBGPSensor.poll()   C

Complexity

Conditions 8

Size

Total Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 8
c 3
b 0
f 0
dl 0
loc 51
rs 5.2591

How to fix   Long Method   

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:

1
from datetime import datetime
2
3
from napalm import get_network_driver
4
5
from st2reactor.sensor.base import PollingSensor
6
7
BGP_PEER_INCREASE = 'napalm.BgpPeerIncrease'
8
BGP_PEER_DECREASE = 'napalm.BgpPeerDecrease'
9
10
11
class NapalmBGPSensor(PollingSensor):
12
13
    def __init__(self, sensor_service, config=None, poll_interval=5):
14
        super(NapalmBGPSensor, self).__init__(sensor_service=sensor_service,
15
                                              config=config,
16
                                              poll_interval=poll_interval)
17
        self._logger = self.sensor_service.get_logger(
18
            name=self.__class__.__name__
19
        )
20
21
        # self._poll_interval = 30
22
23
    def setup(self):
24
        # Need to get initial BGP RIB, neighbor table, etc. Put into "self".
25
        # Then, write some logic within "poll" that checks again, and
26
        # detects diffs
27
        # Detects:
28
        # - Diffs in BGP RIB (need to give a threshold like 100s or 1000s or
29
        #   routes different)
30
        #   (may want to not only store the previous result, but also the
31
        #    previous 10 or so and do a stddev calc)
32
33
        # Stores number of BGP neighbors
34
        # self.bgp_neighbors = 0
35
36
        # Stores RIB information in-between calls
37
        # self.bgp_rib = {}
38
39
        # Dictionary for tracking per-device known state
40
        # Top-level keys are the management IPs sent to NAPALM, and
41
        # information on each is contained below that
42
        self.device_state = {}
43
44
        napalm_config = self._config
45
46
        # Assign sane defaults for configuration
47
        # default_opts = {
48
        #     "opt1": "val1"
49
        # }
50
        # for opt_name, opt_val in default_opts.items():
51
52
        #     try:
53
54
        #         # key exists but is set to nothing
55
        #         if not napalm_config[opt_name]:
56
        #             napalm_config[opt_name] == default_opts
57
58
        #     except KeyError:
59
60
        #         # key doesn't exist
61
        #         napalm_config[opt_name] == default_opts
62
63
        # Assign options to instance
64
        self._devices = napalm_config['devices']
65
66
        # Generate dictionary of device objects per configuration
67
        # IP Address(key): Device Object(value)
68
        self.devices = {
69
            str(device['hostname']): get_network_driver(device['driver'])(
70
                hostname=str(device['hostname']),
71
                username=device['username'],
72
                password=device['password'],
73
                optional_args={
74
                    'port': str(device['port'])
75
                })
76
            for device in self._devices
77
        }
78
79
    def poll(self):
80
81
        for hostname, device_obj in self.devices.items():
82
83
            try:
84
                last_bgp_peers = self.device_state[hostname]["last_bgp_peers"]
85
            except KeyError:
86
87
                # Get current BGP peers (instead of setting to 0 and
88
                # triggering every time sensor starts initially)
89
                try:
90
                    self.device_state[hostname] = {
91
                        "last_bgp_peers": self.get_number_of_peers(device_obj)
92
                    }
93
                    continue
94
                # Any connection-related exception raised here is
95
                # driver-specific, so we have to catch "Exception"
96
                except Exception as e:
97
                    self._logger.debug("Caught exception on connect: %s" % e)
98
                    continue
99
100
            try:
101
                this_bgp_peers = self.get_number_of_peers(device_obj)
102
            # Any connection-related exception raised here is
103
            # driver-specific, so we have to catch "Exception"
104
            except Exception as e:
105
                self._logger.debug("Caught exception on get peers: %s" % e)
106
                continue
107
108
            if this_bgp_peers > last_bgp_peers:
109
                self._logger.info(
110
                    "Peer count went UP to %s" % str(this_bgp_peers)
111
                )
112
                self._bgp_peer_trigger(BGP_PEER_INCREASE, hostname,
113
                                       last_bgp_peers, this_bgp_peers)
114
115
            elif this_bgp_peers < last_bgp_peers:
116
                self._logger.info(
117
                    "BGP neighbors went DOWN to %s" % str(this_bgp_peers)
118
                )
119
120
                self._bgp_peer_trigger(BGP_PEER_DECREASE, hostname,
121
                                       last_bgp_peers, this_bgp_peers)
122
123
            elif this_bgp_peers == last_bgp_peers:
124
                self._logger.info(
125
                    "BGP neighbors STAYED at %s" % str(this_bgp_peers)
126
                )
127
128
            # Save this state for the next poll
129
            self.device_state[hostname]["last_bgp_peers"] = this_bgp_peers
130
131
    def cleanup(self):
132
        # This is called when the st2 system goes down. You can perform
133
        # cleanup operations like closing the connections to external
134
        # system here.
135
        pass
136
137
    def add_trigger(self, trigger):
138
        # This method is called when trigger is created
139
        pass
140
141
    def update_trigger(self, trigger):
142
        # This method is called when trigger is updated
143
        pass
144
145
    def remove_trigger(self, trigger):
146
        # This method is called when trigger is deleted
147
        pass
148
149
    def get_number_of_peers(self, device_obj):
150
151
        vrfs = {}
152
153
        # Retrieve full BGP peer info at the VRF level
154
        try:
155
            with device_obj:
156
                vrfs = device_obj.get_bgp_neighbors()
157
        except Exception:  # TODO(mierdin): convert to ConnectionException
158
            raise
159
160
        this_bgp_peers = 0
161
162
        for vrf_id, vrf_details in vrfs.items():
163
164
            for peer_id, peer_details in vrf_details['peers'].items():
165
166
                # TODO(mierdin): This isn't always the fastest method when
167
                # peers go down. Try to improve on this.
168
                if peer_details['is_up']:
169
                    this_bgp_peers += 1
170
171
        return this_bgp_peers
172
173
    def _bgp_peer_trigger(self, trigger, hostname, oldpeers, newpeers):
174
        trigger = 'napalm.BgpPeerDecrease'
175
        payload = {
176
            'device': hostname,
177
            'oldpeers': int(oldpeers),
178
            'newpeers': int(newpeers),
179
            'timestamp': str(datetime.now()),
180
        }
181
        self._logger.debug("DISPATCHING TRIGGER %s" % trigger)
182
        self._sensor_service.dispatch(trigger=trigger, payload=payload)
183