Completed
Pull Request — master (#487)
by
unknown
02:42
created

OrionBaseAction   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 245
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 245
rs 8.3157
wmc 43

14 Methods

Rating   Name   Duplication   Size   Complexity  
A create() 0 5 1
B node_exists() 0 27 3
A query() 0 5 1
A connect() 0 11 2
A __init__() 0 7 2
A invoke() 0 5 1
A send_user_error() 0 5 1
B get_node_id() 0 29 5
B get_snmp_cred_id() 0 26 3
B get_ncm_transfer_results() 0 33 5
B get_snmp_community() 0 13 5
B get_engine_id() 0 24 3
B status_code_to_text() 0 16 6
B get_ncm_node_id() 0 25 5

How to fix   Complexity   

Complex Class

Complex classes like OrionBaseAction 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
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
2
# contributor license agreements.  See the NOTICE file distributed with
3
# this work for additional information regarding copyright ownership.
4
# The ASF licenses this file to You under the Apache License, Version 2.0
5
# (the "License"); you may not use this file except in compliance with
6
# the License.  You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15
16
import time
17
18
from st2actions.runners.pythonrunner import Action
19
from orionsdk import SwisClient
20
21
22
class OrionBaseAction(Action):
23
    def __init__(self, config):
24
        super(OrionBaseAction, self).__init__(config)
25
26
        self.client = None
27
28
        if "orion" not in self.config:
29
            raise ValueError("Orion host details not in the config.yaml")
30
31
    def connect(self, platform):
32
        """
33
        Connect to an Orion platform from the packs config.yaml.
34
        """
35
        try:
36
            self.client = SwisClient(
37
                self.config['orion'][platform]['host'],
38
                self.config['orion'][platform]['user'],
39
                self.config['orion'][platform]['password'])
40
        except KeyError:
41
            raise ValueError("Orion host details not in the config.yaml")
42
43
    def query(self, swql, **kargs):
44
        """
45
        Run SWQL against the Orion Platform.
46
        """
47
        return self.client.query(swql, **kargs)
48
49
    def invoke(self, entity, verb, *args):
50
        """
51
        Run an Invoke against the Orion Platform.
52
        """
53
        return self.client.invoke(entity, verb, *args)
54
55
    def create(self, entity, **kargs):
56
        """
57
        Run an Create against the Orion Platform.
58
        """
59
        return self.client.create(entity, **kargs)
60
61
    def node_exists(self, caption, ip_address):
62
        """
63
        Check if an Node exists (caption and or ip) on the Orion platform.
64
65
        Returns: True or False.
66
        """
67
        swql = """SELECT NodeID, IPAddress FROM Orion.Nodes
68
                  WHERE Caption=@caption"""
69
        kargs = {'caption': caption}
70
        caption_data = self.query(swql, **kargs)
71
72
        if len(caption_data['results']) >= 1:
73
            self.logger.debug(
74
                "One (or more) Nodes match '{}' Caption.".format(caption))
75
            return True
76
77
        swql = """SELECT NodeID, IPAddress FROM Orion.Nodes
78
                  WHERE IPAddress=@ip_address"""
79
        kargs = {'ip_address': ip_address}
80
        ip_data = self.query(swql, **kargs)
81
82
        if len(ip_data['results']) >= 1:
83
            self.logger.debug(
84
                "One (or more) Nodes match '{}' IP.".format(ip_address))
85
            return True
86
        else:
87
            return False
88
89
    def get_snmp_community(self, community, std_community):
90
        """
91
        Return the correct SNMP comminity to use.
92
        """
93
        if community is not None:
94
            return community
95
        elif std_community is not None:
96
            try:
97
                return self.config['defaults']['snmp'][std_community]
98
            except KeyError:
99
                raise ValueError("Invalid standard community")
100
        elif std_community is None:
101
            raise ValueError("Need one of community or std_community")
102
103
    def get_snmp_cred_id(self, community):
104
        """
105
        Look up an SNMP community in the config and then look up
106
        the Orion ID for the Credential.
107
        """
108
109
        # Check if community is a know standard, otherwise
110
        # use it as the community.
111
        try:
112
            name = self.get_snmp_community(None, community)
113
        except ValueError:
114
            name = community
115
116
        swql = """SELECT ID FROM Orion.Credential
117
        WHERE CredentialType=@CredentialType and Name=@name"""
118
119
        kargs = {'CredentialType':
120
                 'SolarWinds.Orion.Core.Models.Credentials.SnmpCredentialsV2',
121
                 'name': name}
122
        orion_data = self.query(swql, **kargs)
123
124
        if len(orion_data['results']) == 1:
125
            return orion_data['results'][0]['ID']
126
        else:
127
            raise ValueError(
128
                "Failed to lookup community in Orion.Credential!")
129
130
    def get_node_id(self, caption):
131
        """
132
        Gets an NodeID from the Orion platform.
133
134
        Raises: ValueError on muliple or no matching caption.
135
136
        Returns: the NodeID (int)
137
        """
138
        swql = "SELECT NodeID FROM Orion.Nodes WHERE Caption=@caption"
139
        kargs = {'caption': caption}
140
        data = self.query(swql, **kargs)
141
142
        if len(data['results']) == 1:
143
            try:
144
                return data['results'][0]['NodeID']
145
            except IndexError:
146
                raise ValueError("Invalid Node")
147
        elif len(data['results']) >= 2:
148
            self.logger.debug(
149
                "Muliple Nodes match '{}' Caption: {}".format(
150
                    caption, data))
151
            raise ValueError("Muliple Nodes match '{}' Caption".format(
152
                caption))
153
        elif len(data['results']) == 0:
154
            self.logger.debug(
155
                "No Nodes match '{}' Caption: {}".format(
156
                    caption, data))
157
            raise ValueError("No matching Caption for '{}'".format(
158
                caption))
159
160
    def get_engine_id(self, poller):
161
        """
162
        Takes a poller name (or primary) and returns the EngineID for
163
        the poller.
164
165
        Raises: ValueError on an invaild poller.
166
167
        Returns: The EngineID (int)
168
        """
169
170
        if poller == "primary":
171
            return 1
172
        else:
173
            swql = """SELECT EngineID, ServerName, IP, ServerType
174
            FROM Orion.Engines
175
            WHERE ServerName=@poller"""
176
            kargs = {'poller': poller}
177
            data = self.query(swql, **kargs)
178
179
            if len(data['results']) == 1:
180
                return data['results'][0]['EngineID']
181
            else:
182
                self.send_user_error("Invalid poller name")
183
                raise ValueError("Invalid poller name")
184
185
    def get_ncm_node_id(self, caption):
186
        """
187
        Queries the Network configuration Manager nodes table on the Orion
188
        platform for the NodeID of a given node name (aka NodeCaption).
189
190
        Raises: IndexError on Invalid number of nodes (e.g. 0 or 2+).
191
192
        Returns: A single node id.
193
        """
194
195
        swql = "SELECT NodeID FROM Cirrus.Nodes WHERE NodeCaption=@node"
196
        kargs = {'node': caption}
197
        data = self.query(swql, **kargs)
198
199
        if len(data['results']) == 1:
200
            try:
201
                return data['results'][0]['NodeID']
202
            except IndexError:
203
                raise IndexError("Invalid Node")
204
        elif len(data['results']) >= 2:
205
            raise IndexError("Muliple Nodes match '{}' NodeCaption".format(
206
                caption))
207
        elif len(data['results']) == 0:
208
            raise IndexError("No matching NodeCaption for '{}'".format(
209
                caption))
210
211
    def get_ncm_transfer_results(self, transfer_id, sleep_delay=10):
212
        """
213
        Gets the completed (waits until finished). NCM job transfer status
214
        from Orion.
215
216
        Retruns: The completed status.
217
        """
218
        ts = {}
219
        while True:
220
            swql = """SELECT TransferID, Action, Status, ErrorMessage,
221
            DeviceOutput FROM NCM.TransferResults
222
            WHERE TransferID=@transfer_id"""
223
            kargs = {'transfer_id': transfer_id}
224
225
            transfer_data = self.query(swql, **kargs)
226
            status = transfer_data['results'][0]['Status']
227
228
            if status == 1:
229
                time.sleep(sleep_delay)
230
            elif status == 2:
231
                ts['status'] = "Complete"
232
                break
233
            elif status == 3:
234
                ts['status'] = "Error"
235
                ts['ErrorMessage'] = transfer_data['results'][0][
236
                    'ErrorMessage']
237
                break
238
            else:
239
                ts['status'] = "Unknown"
240
                ts['ErrorMessage'] = "Invalid stauts: {}".format(status)
241
                break
242
243
        return ts
244
245
    def status_code_to_text(self, status):
246
        """
247
        Takes an Solarwinds Orion status code and translates it to
248
        human text and also a colour that can be used in Slack.
249
        """
250
251
        if status == 0:
252
            return ("Unknown", "grey")  # aka slack 'grey'
253
        elif status == 1:
254
            return ("Up", "good")  # slack 'good'
255
        elif status == 2:
256
            return ("Down", "#7CD197")  # slack 'danger'
257
        elif status == 3:
258
            return ("Warning", "warning")  # slack 'warning'
259
        elif status == 14:
260
            return ("Critical", "#7CD197")  # slack 'danger'
261
262
    def send_user_error(self, message):
263
        """
264
        Prints an user error message.
265
        """
266
        print(message)
267