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

NapalmBGPSensor.poll()   C

Complexity

Conditions 8

Size

Total Lines 68

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 8
c 2
b 0
f 0
dl 0
loc 68
rs 5.3008

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
    * self.sensor_service
14
        - provides utilities like
15
            get_logger() for writing to logs.
16
            dispatch() for dispatching triggers into the system.
17
    * self._config
18
        - contains configuration that was specified as
19
          config.yaml in the pack.
20
    * self._poll_interval
21
        - indicates the interval between two successive poll() calls.
22
    """
23
24
    def __init__(self, sensor_service, config=None, poll_interval=5):
25
        super(NapalmBGPSensor, self).__init__(sensor_service=sensor_service,
26
                                              config=config,
27
                                              poll_interval=poll_interval)
28
        self._logger = self.sensor_service.get_logger(
29
            name=self.__class__.__name__
30
        )
31
32
        # self._poll_interval = 30
33
34
    def setup(self):
35
        # Setup stuff goes here. For example, you might establish connections
36
        # to external system once and reuse it. This is called only once by the system.
37
38
        # TODO Figure out how to use this and make a config.yaml
39
        #
40
41
        # database = database or self.config.get('default')
42
        # db_config = self.config.get(database, {})
43
        # params = {
44
        #     'database': db_config.get('database') or database,
45
        #     'server': server or db_config.get('server'),
46
        #     'user': user or db_config.get('user'),
47
        #     'password': password or db_config.get('password')
48
        # }
49
50
        # Need to get initial BGP RIB, neighbor table, etc. Put into "self".
51
        # Then, write some logic within "poll" that checks again, and detects diffs
52
        # Detects:
53
        # - Diffs in BGP RIB (need to give a threshold like 100s or 1000s or routes different)
54
        #   (may want to not only store the previous result, but also the previous 10 or so and do a stddev calc)
55
56
        # Stores number of BGP neighbors
57
        # self.bgp_neighbors = 0
58
59
        # Stores RIB information in-between calls
60
        # self.bgp_rib = {}
61
62
        # Dictionary for tracking per-device known state
63
        # Top-level keys are the management IPs sent to NAPALM, and
64
        # information on each is contained below that
65
        self.device_state = {}
66
67
        napalm_config = self._config
68
69
        # Assign sane defaults for configuration
70
        default_opts = {
71
            "opt1": "val1"
72
        }
73
        for opt_name, opt_val in default_opts.items():
74
75
            try:
76
77
                # key exists but is set to nothing
78
                if not napalm_config[opt_name]:
79
                    napalm_config[opt_name] == default_opts
80
81
            except KeyError:
82
83
                # key doesn't exist
84
                napalm_config[opt_name] == default_opts
85
86
        # Assign options to instance
87
        self._devices = napalm_config['devices']
88
89
        # Generate dictionary of device objects per configuration
90
        # IP Address(key): Device Object(value)
91
        #
92
        # TODO(mierdin): Yes I know this looks terrible, I will fix it
93
        self.devices = {
94
            str(device['host']): get_network_driver(device['driver'])(
95
                hostname=str(device['host']),
96
                username=device['username'],
97
                password=device['password'],
98
                optional_args={
99
                    'port': str(device['port'])
100
                })
101
            for device in self._devices
102
        }
103
104
    def poll(self):
105
        # This is where the crux of the sensor work goes.
106
        # This is called every self._poll_interval.
107
        # For example, let's assume you want to query ec2 and get
108
        # health information about your instances:
109
        #   some_data = aws_client.get('')
110
        #   payload = self._to_payload(some_data)
111
        #   # _to_triggers is something you'd write to convert the data format you have
112
        #   # into a standard python dictionary. This should follow the payload schema
113
        #   # registered for the trigger.
114
        #   self.sensor_service.dispatch(trigger, payload)
115
        #   # You can refer to the trigger as dict
116
        #   # { "name": ${trigger_name}, "pack": ${trigger_pack} }
117
        #   # or just simply by reference as string.
118
        #   # i.e. dispatch(${trigger_pack}.${trigger_name}, payload)
119
        #   # E.g.: dispatch('examples.foo_sensor', {'k1': 'stuff', 'k2': 'foo'})
120
        #   # trace_tag is a tag you would like to associate with the dispacthed TriggerInstance
121
        #   # Typically the trace_tag is unique and a reference to an external event.
122
123
        for hostname, device_obj in self.devices.items():
124
125
            try:
126
                last_bgp_peers = self.device_state[hostname]["last_bgp_peers"]
127
            except KeyError:
128
129
                # Get current BGP peers (instead of setting to 0 and
130
                # triggering every time sensor starts initially)
131
                try:
132
                    self.device_state[hostname] = {
133
                        "last_bgp_peers": self.get_number_of_peers(device_obj)
134
                    }
135
                    continue
136
                # Any connection-related exception raised here is
137
                # driver-specific, so we have to catch "Exception"
138
                except Exception as e:
139
                    self._logger.debug("Caught exception on connect: %s" % e)
140
                    continue
141
142
            try:
143
                this_bgp_peers = self.get_number_of_peers(device_obj)
144
            # Any connection-related exception raised here is
145
            # driver-specific, so we have to catch "Exception"
146
            except Exception as e:
147
                self._logger.debug("Caught exception on get peers: %s" % e)
148
                continue
149
150
            if this_bgp_peers > last_bgp_peers:
151
                self._logger.info(
152
                    "Peer count went UP to %s" % str(this_bgp_peers)
153
                )
154
                self._bgp_peer_trigger(BGP_PEER_INCREASE, hostname,
155
                                       last_bgp_peers, this_bgp_peers)
156
157
            elif this_bgp_peers < last_bgp_peers:
158
                self._logger.info(
159
                    "BGP neighbors went DOWN to %s" % str(this_bgp_peers)
160
                )
161
162
                self._bgp_peer_trigger(BGP_PEER_DECREASE, hostname,
163
                                       last_bgp_peers, this_bgp_peers)
164
165
            elif this_bgp_peers == last_bgp_peers:
166
                self._logger.info(
167
                    "BGP neighbors STAYED at %s" % str(this_bgp_peers)
168
                )
169
170
            # Save this state for the next poll
171
            self.device_state[hostname]["last_bgp_peers"] = this_bgp_peers
172
173
    def cleanup(self):
174
        # This is called when the st2 system goes down. You can perform
175
        # cleanup operations like closing the connections to external
176
        # system here.
177
        pass
178
179
    def add_trigger(self, trigger):
180
        # This method is called when trigger is created
181
        pass
182
183
    def update_trigger(self, trigger):
184
        # This method is called when trigger is updated
185
        pass
186
187
    def remove_trigger(self, trigger):
188
        # This method is called when trigger is deleted
189
        pass
190
191
    def get_number_of_peers(self, device_obj):
192
193
        vrfs = {}
194
195
        # Retrieve full BGP peer info at the VRF level
196
        try:
197
            with device_obj:
198
                vrfs = device_obj.get_bgp_neighbors()
199
        except Exception:  # TODO(mierdin): convert to ConnectionException
200
            raise
201
202
        this_bgp_peers = 0
203
204
        for vrf_id, vrf_details in vrfs.items():
205
206
            for peer_id, peer_details in vrf_details['peers'].items():
207
208
                # TODO(mierdin): This isn't always the fastest method when
209
                # peers go down. Try to improve on this.
210
                if peer_details['is_up']:
211
                    this_bgp_peers += 1
212
213
        return this_bgp_peers
214
215
    def _bgp_peer_trigger(self, trigger, hostname, oldpeers, newpeers):
216
        trigger = 'napalm.BgpPeerDecrease'
217
        payload = {
218
            'device': hostname,
219
            'oldpeers': int(oldpeers),
220
            'newpeers': int(newpeers),
221
            'timestamp': str(datetime.now()),
222
        }
223
        self._logger.debug("DISPATCHING TRIGGER %s" % trigger)
224
        self._sensor_service.dispatch(trigger=trigger, payload=payload)
225