Bsd.listVtms()   F
last analyzed

Complexity

Conditions 9

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
dl 0
loc 29
rs 3
c 0
b 0
f 0
1
#!/usr/bin/python
2
3
import requests
4
import sys
5
import json
6
import time
7
8
9
class Vadc:
10
11
    DEBUG = False
12
13
    def __init__(self, host, user, passwd, logger):
14
        requests.packages.urllib3.disable_warnings()
15
        self.host = host
16
        self.user = user
17
        self.passwd = passwd
18
        self.logger = logger
19
        self.client = None
20
        self._cache = {}
21
22
    def _debug(self, message):
23
        if Vadc.DEBUG:
24
            self.logger.debug(message)
25
26
    def _initHTTP(self):
27
        self.client = requests.Session()
28
        self.client.auth = (self.user, self.passwd)
29
30
    def _getConfig(self, url):
31
        self._debug("URL: " + url)
32
        try:
33
            self._initHTTP()
34
            response = self.client.get(url, verify=False)
35
        except:
36
            self.logger.error("Error: Unable to connect to API")
37
            raise Exception("Error: Unable to connect to API")
38
        self._debug("Status: {}".format(response.status_code))
39
        self._debug("Body: " + response.text)
40
        return response
41
42
    def _pushConfig(self, url, config, method="PUT", ct="application/json"):
43
        self._debug("URL: " + url)
44
        try:
45
            self._initHTTP()
46
            config = json.dumps(config)
47
            if method == "PUT":
48
                response = self.client.put(url, verify=False, data=config,
49
                    headers={"Content-Type": ct})
50
            else:
51
                response = self.client.post(url, verify=False, data=config,
52
                    headers={"Content-Type": ct})
53
        except requests.exceptions.ConnectionError:
54
            self.logger.error("Error: Unable to connect to API")
55
            raise Exception("Error: Unable to connect to API")
56
57
        self._debug("DATA: " + config)
58
        self._debug("Status: {}".format(response.status_code))
59
        self._debug("Body: " + response.text)
60
        return response
61
62
    def _delConfig(self, url):
63
        self._debug("URL: " + url)
64
        try:
65
            self._initHTTP()
66
            response = self.client.delete(url, verify=False)
67
        except requests.exceptions.ConnectionError:
68
            sys.stderr.write("Error: Unable to connect to API {}".format(url))
69
            raise Exception("Error: Unable to connect to API")
70
71
        self._debug("Status: {}".format(response.status_code))
72
        self._debug("Body: " + response.text)
73
        return response
74
75
    def _dictify(self, listing, keyName):
76
        dictionary = {}
77
        for item in listing:
78
            k = item.pop(keyName)
79
            dictionary[k] = item
80
81
    def _cacheStore(self, key, data, timeout=10):
82
        exp = time.time() + timeout
83
        self._debug("Cache Store: {}".format(key))
84
        self._cache[key] = {"exp": exp, "data": data}
85
86
    def _cacheLookup(self, key):
87
        now = time.time()
88
        if key in self._cache:
89
            entry = self._cache[key]
90
            if entry["exp"] > now:
91
                self._debug("Cache Hit: {}".format(key))
92
                return entry["data"]
93
        self._debug("Cache Miss: {}".format(key))
94
        return None
95
96
    def dumpCache(self):
97
        return json.dumps(self._cache, encoding="utf-8")
98
99
    def loadCache(self, cache):
100
        self._cache = json.loads(cache, encoding="utf-8")
101
102
103
class Bsd(Vadc):
104
105
    def __init__(self, config, logger):
106
107
        try:
108
            host = config['brcd_sd_host']
109
            user = config['brcd_sd_user']
110
            passwd = config['brcd_sd_pass']
111
        except KeyError:
112
            raise ValueError("brcd_sd_host, brcd_sd_user, and brcd_sd_pass must be configured")
113
114
        Vadc.__init__(self, host, user, passwd, logger)
115
        if host.endswith('/') == False:
116
            host += "/"
117
        self.baseUrl = host + "api/tmcm/2.2"
118
119
    def addVtm(self, vtm, password, address, bw, fp):
120
        url = self.baseUrl + "/instance/?managed=false"
121
122
        if address is None:
123
            address = vtm
124
125
        config = {"bandwidth": bw, "tag": vtm, "owner": "stanley", "stm_feature_pack": fp,
126
            "rest_address": address + ":9070", "admin_username": "admin", "rest_enabled": False,
127
            "host_name": address, "management_address": address}
128
129
        if password is not None:
130
            config["admin_password"] = password
131
            config["rest_enabled"] = True
132
            config["license_name"] = "universal_v3"
133
134
        res = self._pushConfig(url, config, "POST")
135
        if res.status_code != 201:
136
            raise Exception("Failed to add vTM. Response: {}, {}".format(res.status_code, res.text))
137
        return res.json()
138
139
    def delVtm(self, vtm):
140
        url = self.baseUrl + "/instance/" + vtm
141
        config = {"status": "deleted"}
142
        res = self._pushConfig(url, config, "POST")
143
        if res.status_code != 200:
144
            raise Exception("Failed to del vTM. Response: {}, {}".format(res.status_code, res.text))
145
        return res.json()
146
147
    def getVtm(self, tag):
148
        vtm = self._cacheLookup("getVtm_" + tag)
149
        if vtm is None:
150
            url = self.baseUrl + "/instance/" + tag
151
            res = self._getConfig(url)
152
            if res.status_code != 200:
153
                raise Exception("Failed to get vTM {}. Response: {}, {}".format(
154
                    vtm, res.status_code, res.text))
155
            vtm = res.json()
156
            self._cacheStore("getVtm_" + tag, vtm)
157
        return vtm
158
159
    def listVtms(self, full, deleted, stringify):
160
        instances = self._cacheLookup("listVtms")
161
        if instances is None:
162
            url = self.baseUrl + "/instance/"
163
            res = self._getConfig(url)
164
            if res.status_code != 200:
165
                raise Exception("Failed to list vTMs. Response: {}, {}".format(
166
                    res.status_code, res.text))
167
            instances = res.json()
168
            self._cacheStore("listVtms", instances)
169
170
        output = []
171
        for instance in instances["children"]:
172
            config = self.getVtm(instance["name"])
173
            if deleted is False and config["status"] == "Deleted":
174
                continue
175
            if full:
176
                config["name"] = instance["name"]
177
                output.append(config)
178
            else:
179
                out_dict = {k: config[k] for k in ("host_name", "tag", "status",
180
                    "stm_feature_pack", "bandwidth")}
181
                out_dict["name"] = instance["name"]
182
                output.append(out_dict)
183
184
        if stringify:
185
            return json.dumps(output, encoding="utf-8")
186
        else:
187
            return output
188
189
    def getStatus(self, vtm=None, stringify=False):
190
        instances = self._cacheLookup("getStatus")
191
        if instances is None:
192
            url = self.baseUrl + "/monitoring/instance"
193
            res = self._getConfig(url)
194
            if res.status_code != 200:
195
                raise Exception("Failed get Status. Result: {}, {}".format(
196
                    res.status_code, res.text))
197
198
            instances = res.json()
199
            self._cacheStore("getStatus", instances)
200
201
        if vtm is not None:
202
            for instance in instances:
203
                if instance["tag"] != vtm and instance["name"] != vtm:
204
                    instances.remove(instance)
205
206
        if stringify:
207
            return json.dumps(instances, encoding="utf-8")
208
        else:
209
            return instances
210
211
    def getErrors(self, stringify=False):
212
        instances = self.getStatus()
213
        errors = {}
214
        for instance in instances:
215
            error = {}
216
            self._debug(instance)
217
            if instance["id_health"]["alert_level"] != 1:
218
                error["id_health"] = instance["id_health"]
219
            if instance["rest_access"]["alert_level"] != 1:
220
                error["rest_access"] = instance["rest_access"]
221
            if instance["licensing_activity"]["alert_level"] != 1:
222
                error["licensing_activity"] = instance["licensing_activity"]
223
            if instance["traffic_health"]["error_level"] != "ok":
224
                error["traffic_health"] = instance["traffic_health"]
225
            if len(error) != 0:
226
                error["tag"] = instance["tag"]
227
                error["name"] = instance["name"]
228
                if "traffic_health" in error:
229
                    if "virtual_servers" in error["traffic_health"]:
230
                        del error["traffic_health"]["virtual_servers"]
231
                errors[instance["name"]] = error
232
233
        if stringify:
234
            return json.dumps(errors, encoding="utf-8")
235
        else:
236
            return errors
237
238
    def getMonitorIntervals(self, setting=None):
239
        intervals = self._cacheLookup("getMonitorIntervals")
240
        if intervals is None:
241
            url = self.baseUrl + "/settings/monitoring"
242
            res = self._getConfig(url)
243
            if res.status_code != 200:
244
                raise Exception("Failed to get Monitoring Intervals. Result: {}, {}".format(
245
                    res.status_code, res.text))
246
247
            intervals = res.json()
248
            self._cacheStore("getMonitorIntervals", intervals)
249
250
        if setting is not None:
251
            if setting not in intervals:
252
                raise Exception("Setting: {} does not exist.".format(setting))
253
            return intervals[setting]
254
        return intervals
255
256
    def getBandwidth(self, vtm=None, stringify=False):
257
        instances = self.getStatus(vtm)
258
        bandwidth = {}
259
        for instance in instances:
260
            config = self.getVtm(instance["name"])
261
            tag = config["tag"]
262
            # Bytes/Second
263
            if "throughput_out" in instance:
264
                current = (instance["throughput_out"] / 1000000.0) * 8
265
            else:
266
                current = 0.0
267
            # Mbps
268
            assigned = config["bandwidth"]
269
            # Bytes/Second
270
            if "metrics_peak_throughput" in config:
271
                peak = (config["metrics_peak_throughput"] / 1000000.0) * 8
272
            else:
273
                peak = 0.0
274
            bandwidth[instance["name"]] = {"tag": tag, "current": current,
275
                "assigned": assigned, "peak": peak}
276
277
        if stringify:
278
            return json.dumps(bandwidth, encoding="utf-8")
279
        else:
280
            return bandwidth
281
282
    def setBandwidth(self, vtm, bw):
283
        url = self.baseUrl + "/instance/" + vtm
284
        config = {"bandwidth": bw}
285
        res = self._pushConfig(url, config)
286
        if res.status_code != 200:
287
            raise Exception("Failed to set Bandwidth. Result: {}, {}".format(
288
                res.status_code, res.text))
289
        config = res.json()
290
        return config
291
292
293
class Vtm(Vadc):
294
295
    def __init__(self, config, logger, vtm):
296
297
        try:
298
            self._proxy = config['brcd_sd_proxy']
299
            if self._proxy:
300
                host = config['brcd_sd_host']
301
                user = config['brcd_sd_user']
302
                passwd = config['brcd_sd_pass']
303
            else:
304
                host = config['brcd_vtm_host']
305
                user = config['brcd_vtm_user']
306
                passwd = config['brcd_vtm_pass']
307
        except KeyError:
308
            raise ValueError("You must set key brcd_sd_proxy, and either " +
309
                "brcd_sd_[host|user|pass] or brcd_vtm_[host|user|pass].")
310
311
        Vadc.__init__(self, host, user, passwd, logger)
312
        if host.endswith('/') == False:
313
            host += "/"
314
        if self._proxy:
315
            self.baseUrl = host + "api/tmcm/2.2/instance/{}/tm/3.8/config/active".format(vtm)
316
        else:
317
            self.baseUrl = host + "api/tm/3.8/config/active"
318
319
    def _getNodeTable(self, name):
320
        url = self.baseUrl + "/pools/" + name
321
        res = self._getConfig(url)
322
        if res.status_code != 200:
323
            raise Exception("Failed to get pool. Result: {}, {}".format(res.status_code, res.text))
324
325
        config = res.json()
326
        return config["properties"]["basic"]["nodes_table"]
327
328
    def _getVSConfig(self, name):
329
        url = self.baseUrl + "/virtual_servers/" + name
330
        res = self._getConfig(url)
331
        if res.status_code != 200:
332
            raise Exception("Failed to get VS. Result: {}, {}".format(res.status_code, res.text))
333
334
        config = res.json()
335
        return config
336
337
    def _setVSConfig(self, name, config):
338
        url = self.baseUrl + "/virtual_servers/" + name
339
        res = self._pushConfig(url, config)
340
        if res.status_code != 200:
341
            raise Exception("Failed to set VS. Result: {}, {}".format(res.status_code, res.text))
342
        config = res.json()
343
        return config
344
345
    def _getVSRules(self, name):
346
        config = self._getVSConfig(name)
347
        rules = {k: config["properties"]["basic"][k] for k in
348
                ("request_rules", "response_rules", "completionrules")}
349
        return rules
350
351
    def _setVSRules(self, name, rules):
352
        config = {"properties": {"basic": rules}}
353
        res = self._setVSConfig(name, config)
354
        if res.status_code != 200:
355
            raise Exception("Failed set VS Rules. Result: {}, {}".format(res.status_code, res.text))
356
357
    def insertRule(self, vsname, rulename, insert=True):
358
        rules = self._getVSRules(vsname)
359
        if insert:
360
            if rulename in rules["request_rules"]:
361
                raise Exception("VServer {} already in maintenance".format(vsname))
362
            rules["request_rules"].insert(0, rulename)
363
        else:
364
            if rulename not in rules["request_rules"]:
365
                raise Exception("VServer {} is not in maintenance".format(vsname))
366
            rules["request_rules"].remove(rulename)
367
        self._setVSRules(vsname, rules)
368
369
    def enableMaintenance(self, vsname, rulename="maintenance", enable=True):
370
        self.insertRule(vsname, rulename, enable)
371
372
    def getPoolNodes(self, name):
373
        nodeTable = self._getNodeTable(name)
374
        nodes = {"active": [], "disabled": [], "draining": []}
375
        for node in nodeTable:
376
            if node["state"] == "active":
377
                nodes["active"].append(node["node"])
378
            elif node["state"] == "disabled":
379
                nodes["disabled"].append(node["node"])
380
            elif node["state"] == "draining":
381
                nodes["draining"].append(node["node"])
382
            else:
383
                self.logger.warn("Unknown Node State: {}".format(node["state"]))
384
385
        return nodes
386
387
    def drainNodes(self, name, nodes, drain=True):
388
        url = self.baseUrl + "/pools/" + name
389
        nodeTable = self._getNodeTable(name)
390
        for entry in nodeTable:
391
            if entry["node"] in nodes:
392
                if drain:
393
                    entry["state"] = "draining"
394
                else:
395
                    entry["state"] = "active"
396
397
        config = {"properties": {"basic": {"nodes_table": nodeTable}}}
398
        res = self._pushConfig(url, config)
399
        if res.status_code != 201 and res.status_code != 200:
400
            raise Exception("Failed to add pool. Result: {}, {}".format(res.status_code, res.text))
401
402
    def addPool(self, name, nodes, algorithm, persistence, monitors):
403
        url = self.baseUrl + "/pools/" + name
404
405
        nodeTable = []
406
        for node in nodes:
407
            nodeTable.append({"node": node, "state": "active"})
408
409
        config = {"properties": {"basic": {"nodes_table": nodeTable, "monitors": monitors,
410
            "persistence_class": persistence}, "load_balancing": {"algorithm": algorithm}}}
411
412
        res = self._pushConfig(url, config)
413
        if res.status_code != 201 and res.status_code != 200:
414
            raise Exception("Failed to add pool. Result: {}, {}".format(res.status_code, res.text))
415
416
    def delPool(self, name):
417
        url = self.baseUrl + "/pools/" + name
418
        res = self._delConfig(url)
419
        if res.status_code != 204:
420
            raise Exception("Failed to del pool. Result: {}, {}".format(res.status_code, res.text))
421
422
    def addVserver(self, name, pool, tip, port, protocol):
423
        url = self.baseUrl + "/virtual_servers/" + name
424
        config = {"properties": {"basic": {"pool": pool, "port": port, "protocol": protocol,
425
            "listen_on_any": False, "listen_on_traffic_ips": [tip], "enabled": True}}}
426
427
        res = self._pushConfig(url, config)
428
        if res.status_code != 201:
429
            raise Exception("Failed to add VS. Result: {}, {}".format(res.status_code, res.text))
430
431
    def delVserver(self, name):
432
        url = self.baseUrl + "/virtual_servers/" + name
433
        res = self._delConfig(url)
434
        if res.status_code != 204:
435
            raise Exception("Failed to del VS. Result: {}, {}".format(res.status_code, res.text))
436
437
    def addTip(self, name, vtms, addresses):
438
        url = self.baseUrl + "/traffic_ip_groups/" + name
439
440
        config = {"properties": {"basic": {"ipaddresses": addresses,
441
            "machines": vtms, "enabled": True}}}
442
443
        res = self._pushConfig(url, config)
444
        if res.status_code != 201:
445
            raise Exception("Failed to add TIP. Result: {}, {}".format(res.status_code, res.text))
446
447
    def delTip(self, name):
448
        url = self.baseUrl + "/traffic_ip_groups/" + name
449
        res = self._delConfig(url)
450
        if res.status_code != 204:
451
            raise Exception("Failed to del TIP. Result: {}, {}".format(res.status_code, res.text))
452
453
    def addServerCert(self, name, public, private):
454
        url = self.baseUrl + "/ssl/server_keys/" + name
455
456
        public = public.replace("\\n", "\n")
457
        private = private.replace("\\n", "\n")
458
459
        config = {"properties": {"basic": {"public": public, "private": private}}}
460
461
        res = self._pushConfig(url, config)
462
        if res.status_code != 201 and res.status_code != 200:
463
            raise Exception("Failed to add Server Certificate." +
464
                " Result: {}, {}".format(res.status_code, res.text))
465
466
    def delServerCert(self, name):
467
        url = self.baseUrl + "/ssl/server_keys/" + name
468
        res = self._delConfig(url)
469
        if res.status_code != 204:
470
            raise Exception("Failed to delete Server Certificate." +
471
                " Result: {}, {}".format(res.status_code, res.text))
472
473
    def enableSSLOffload(self, name, cert="", on=True, xproto=False, headers=False):
474
        url = self.baseUrl + "/virtual_servers/" + name
475
        config = {"properties": {"basic": {"ssl_decrypt": on, "add_x_forwarded_proto": xproto},
476
            "ssl": {"add_http_headers": headers, "server_cert_default": cert}}}
477
478
        res = self._pushConfig(url, config)
479
        if res.status_code != 200:
480
            raise Exception("Failed to configure SSl Offload on {}.".format(name) +
481
                " Result: {}, {}".format(res.status_code, res.text))
482
483
    def enableSSLEncryption(self, name, on=True, verify=False):
484
        url = self.baseUrl + "/pools/" + name
485
        config = {"properties": {"ssl": {"enable": on, "strict_verify": verify}}}
486
487
        res = self._pushConfig(url, config)
488
        if res.status_code != 200:
489
            raise Exception("Failed to configure SSl Encryption on {}.".format(name) +
490
                " Result: {}, {}".format(res.status_code, res.text))
491
492
    def addSessionPersistence(self, name, method, cookie=None):
493
        types = ["ip", "universal", "named", "transparent", "cookie", "j2ee", "asp", "ssl"]
494
        if method not in types:
495
            raise Exception("Failed to add SP Class. Invalid method: {}".format(method) +
496
                "Must be one of: {}".format(types))
497
        if method == "cookie" and cookie is None:
498
            raise Exception("Failed to add SP Class. You must provide a cookie name.")
499
500
        if cookie is None:
501
            cookie = ""
502
503
        url = self.baseUrl + "/persistence/" + name
504
        config = {"properties": {"basic": {"type": method, "cookie": cookie}}}
505
506
        res = self._pushConfig(url, config)
507
        if res.status_code != 201 and res.status_code != 200:
508
            raise Exception("Failed to add Session Persistence Class" +
509
                " Result: {}, {}".format(res.status_code, res.text))
510
511
    def delSessionPersistence(self, name):
512
        url = self.baseUrl + "/persistence/" + name
513
        res = self._delConfig(url)
514
        if res.status_code != 204:
515
            raise Exception("Failed to delete Session Persistence Class." +
516
                " Result: {}, {}".format(res.status_code, res.text))
517