1 | #!/usr/bin/python3 |
||
2 | """ |
||
3 | This code was created to integrate Kytos napps with Zabbix |
||
4 | |||
5 | Created on: Dec/2020 |
||
6 | Author: Italo Valcy |
||
7 | |||
8 | Changelog: |
||
9 | - 2020/12/10 - Creating the script for monitoring items of Kytos similar to oess_zabbix.py |
||
10 | - 2020/12/15 - adding option to export EVC statistics (bytes and packets) |
||
11 | - 2022/11/30 - bug fixes and integration with kytos-ng/flow_stats rather than flow_manager |
||
12 | - 2023/05/11 - integration with kytos-ng/kytos_stats rather than flow_stats |
||
13 | |||
14 | """ |
||
15 | |||
16 | import os |
||
17 | import sys |
||
18 | import json |
||
19 | import argparse |
||
20 | import requests |
||
21 | import time |
||
22 | |||
23 | url='http://192.168.0.1:8181/api' |
||
24 | |||
25 | CACHE_DIR='/var/cache/kytos_zabbix' |
||
26 | cache = { |
||
27 | 1: CACHE_DIR + '/nodes.json', |
||
28 | 2: CACHE_DIR + '/links.json', |
||
29 | 3: CACHE_DIR + '/evcs.json', |
||
30 | 4: CACHE_DIR + '/flows.json', |
||
31 | 5: CACHE_DIR + '/flows.json', |
||
32 | 6: CACHE_DIR + '/tables.json', |
||
33 | } |
||
34 | |||
35 | help_msg = "Kytos wrapper for Zabbix" |
||
36 | |||
37 | parser = argparse.ArgumentParser() |
||
38 | parser.add_argument("-l", "--url", dest="url", help="URL for your Kytos REST API", default=url) |
||
39 | parser.add_argument("-u", "--user", dest="username", help="Username to authenticate into Kytos API") |
||
40 | parser.add_argument("-p", "--pass", dest="password", help="Password to authenticate into Kytos API") |
||
41 | parser.add_argument("-f", "--authfile", dest="authfile", help="Authentication file containing username (first line) and password (second line) to authenticate into Kytos API") |
||
42 | parser.add_argument("-T", "--timeout", dest="timeout", type=int, help="You can tell Requests to stop waiting for a response after a given number of seconds", default=5) |
||
43 | parser.add_argument("-c", "--cache_policy", dest="cache_policy", default=60, help="Cache policy: never, always or X seconds (default to cache for 600 seconds)") |
||
44 | parser.add_argument("-o", "--monitoring_option", dest="option", type=int, default=1, choices=[1, 2, 3, 4, 5, 6], help="Monitoring option: 1 - for monitor nodes, 2 - for monitor links, 3 - for monitor evcs (status), 4 - evc statistics, 5 - OpenFlow flows stats, 6 - OpenFlow tables stats") |
||
45 | parser.add_argument("-t", "--target", dest="target", help="Item status (0-down/others, 1-disabled, 2-up/primary, 3-up/backup). Argument is the item id to be monitored (depending on the -o option).") |
||
46 | parser.add_argument("-z", "--zabbix_output", dest="count_output", type=int, choices=[1, 2], help="Zabbix LLD: (1) Count number of lines in each output or (2) list-only registers", default=None) |
||
47 | parser.add_argument("-s", "--stats", dest="stats", type=int, default=1, choices=[1, 2, 3, 4, 5], help="EVC statistics type: 1 - bytes/UNI_A, 2 - bytes/UNI_Z , 3 - packets/UNI_A, 4 - packets/UNI_Z. Table statistics type: 5 - active count, default - number of tables") |
||
48 | |||
49 | args = parser.parse_args() |
||
50 | |||
51 | if args.option == 4 and not args.target: |
||
52 | sys.stderr.write('error: to print statistics it requires -t\n') |
||
53 | parser.print_help() |
||
54 | sys.exit(2) |
||
55 | |||
56 | if args.authfile: |
||
57 | authdata = open(args.authfile).readlines() |
||
58 | args.username = authdata[0].strip() |
||
59 | args.password = authdata[1].strip() |
||
60 | |||
61 | args.url = os.environ.get("KYTOS_URL", args.url) |
||
62 | args.timeout = int(os.environ.get("KYTOS_TIMEOUT", args.timeout)) |
||
63 | args.username = os.environ.get("KYTOS_USERNAME", args.username) |
||
64 | args.password = os.environ.get("KYTOS_PASSWORD", args.password) |
||
65 | |||
66 | def is_valid_cache(option): |
||
67 | filename = cache[option] |
||
68 | if not os.path.isfile(filename) or os.path.getsize(filename) == 0: |
||
69 | return False |
||
70 | if args.cache_policy == 'never': |
||
71 | return False |
||
72 | elif args.cache_policy == 'always': |
||
73 | return True |
||
74 | try: |
||
75 | tmout = int(args.cache_policy) |
||
76 | except: |
||
77 | tmout = 600 |
||
78 | return os.stat(filename).st_mtime > time.time() - tmout |
||
79 | |||
80 | def get_data(option, url): |
||
81 | if is_valid_cache(option): |
||
82 | with open(cache[option], 'r') as file: |
||
83 | return file.read() |
||
84 | |||
85 | auth = None |
||
86 | if args.username and args.password: |
||
87 | auth = (args.username, args.password) # assume HTTP Basic |
||
88 | |||
89 | try: |
||
90 | response = requests.get(url, auth=auth, timeout=args.timeout) |
||
91 | assert response.status_code == 200, response.text |
||
92 | data = response.text |
||
93 | except Exception as e: |
||
94 | print("ERROR: failed to get data from URL=%s: %s" % (url, e)) |
||
95 | sys.exit(2) |
||
96 | |||
97 | if args.cache_policy != 'never': |
||
98 | with open(cache[option], 'w') as file: |
||
99 | file.write(data) |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
100 | return data |
||
101 | |||
102 | def get_kytos_data(url, option): |
||
103 | API_ENDPOINT = "" |
||
104 | if option == 1: |
||
105 | API_ENDPOINT="/kytos/topology/v3/switches" |
||
106 | elif option == 2: |
||
107 | API_ENDPOINT="/kytos/topology/v3/links" |
||
108 | elif option == 3: |
||
109 | API_ENDPOINT="/kytos/mef_eline/v2/evc" |
||
110 | elif option in [4,5]: |
||
111 | API_ENDPOINT="/amlight/kytos_stats/v1/flow/stats" |
||
112 | elif option in [6]: |
||
113 | API_ENDPOINT="/amlight/kytos_stats/v1/table/stats" |
||
114 | |||
115 | data = get_data(option, url + API_ENDPOINT) |
||
116 | try: |
||
117 | data = json.loads(data) |
||
118 | if option == 1: |
||
119 | data = data["switches"] |
||
120 | elif option == 2: |
||
121 | data = data["links"] |
||
122 | except Exception as e: |
||
123 | print("ERROR: failed to get data from URL=%s: %s" % (url + API_ENDPOINT, e)) |
||
124 | sys.exit(2) |
||
125 | |||
126 | return data |
||
127 | |||
128 | def convert_status(active, enabled): |
||
129 | if active: |
||
130 | return "2" |
||
131 | elif not enabled: |
||
132 | return "1" |
||
133 | else: |
||
134 | return "0" |
||
135 | |||
136 | def compare_paths(path1, path2): |
||
137 | if len(path1) != len(path2): |
||
138 | return False |
||
139 | return all(x.get('id') == y.get('id') for x,y in zip(path1, path2)) |
||
140 | |||
141 | def print_target_results(data, option, target): |
||
142 | if target not in data: |
||
143 | print("Unknown target=%s" % (target)) |
||
144 | return |
||
145 | |||
146 | if option == 1: |
||
147 | sw = data[target] |
||
148 | print(convert_status(sw["active"], sw["enabled"])) |
||
149 | elif option == 2: |
||
150 | link = data[target] |
||
151 | print(convert_status(link["active"], link["enabled"])) |
||
152 | elif option == 3: |
||
153 | evc = data[target] |
||
154 | status = convert_status(evc["active"], evc["enabled"]) |
||
155 | if status != "2" or evc.get("dynamic_backup_path", False): |
||
156 | print(status) |
||
157 | return |
||
158 | if compare_paths(evc["current_path"], evc["primary_path"]): |
||
159 | print("2") |
||
160 | elif compare_paths(evc["current_path"], evc["backup_path"]): |
||
161 | print("3") |
||
162 | else: |
||
163 | print("0") |
||
164 | |||
165 | def print_flow_stats(data, target): |
||
166 | if target: |
||
167 | print(len(data.get(target, []))) |
||
168 | else: |
||
169 | l = 0 |
||
170 | for s in data: |
||
171 | l+= len(data[s]) |
||
172 | print(l) |
||
173 | |||
174 | def print_tables_stats(data, target, stats_type): |
||
175 | if target: |
||
176 | split = target.split('::') |
||
177 | dpid = split[0] |
||
178 | tables = data.get(dpid, []) |
||
179 | if len(split) > 1: |
||
180 | table_id = split[1] |
||
181 | if table_id in tables: |
||
182 | tables = {table_id: tables.get(table_id)} |
||
183 | else: |
||
184 | tables = {} |
||
185 | if stats_type != 5: |
||
186 | print(len(tables)) |
||
187 | else: |
||
188 | stats = {table_id:table.get('active_count') for table_id, table in tables.items()} |
||
189 | print(stats) |
||
190 | else: |
||
191 | l = 0 |
||
192 | for s in data: |
||
193 | l+= len(data[s]) |
||
194 | print(l) |
||
195 | |||
196 | def print_stats(flows, target, stats_type): |
||
197 | evcs = get_kytos_data(args.url, 3) |
||
198 | if target not in evcs: |
||
199 | print("Unknown target=%s" % (target)) |
||
200 | return |
||
201 | |||
202 | uni_field = 'uni_a' |
||
203 | if stats_type in [2,4]: |
||
204 | uni_field = 'uni_z' |
||
205 | stats_field = 'byte_count' |
||
206 | if stats_type in [3, 4]: |
||
207 | stats_field = 'packet_count' |
||
208 | |||
209 | uni = evcs[target][uni_field]['interface_id'].split(':') |
||
210 | sw = ':'.join(uni[:-1]) |
||
211 | iface = int(uni[-1]) |
||
212 | try: |
||
213 | tag = evcs[target][uni_field]['tag']['value'] |
||
214 | except: |
||
215 | tag = None |
||
216 | |||
217 | result = 0 |
||
218 | for flow_id, flow in flows.get(sw, {}).items(): |
||
219 | if format(flow['cookie'], 'x')[2:] == target and flow['match']['in_port'] == iface and flow['match'].get('dl_vlan') == tag: |
||
220 | result += flow[stats_field] |
||
221 | |||
222 | print(result) |
||
223 | |||
224 | def list_items(data, option): |
||
225 | result = {"data":[]} |
||
226 | if option == 1: |
||
227 | for sw in data: |
||
228 | name = data[sw]["metadata"].get("name", None) |
||
229 | if not name: |
||
230 | name = data[sw]["name"] |
||
231 | result["data"].append({"{#OFSWID}": sw, "{#OFSWNAME}": name}) |
||
232 | elif option == 2: |
||
233 | for l in data: |
||
234 | link = data[l] |
||
235 | name = link.get("name", None) |
||
236 | if not name: |
||
237 | name = link["endpoint_a"]["name"] + "_" + link["endpoint_b"]["name"] |
||
238 | result["data"].append({"{#LINKID}": l, "{#LINKNAME}": name}) |
||
239 | elif option == 3: |
||
240 | for evc in data: |
||
241 | result["data"].append({"{#EVCID}": evc, "{#EVCNAME}": data[evc]["name"]}) |
||
242 | print(json.dumps(result)) |
||
243 | |||
244 | data = get_kytos_data(args.url, args.option) |
||
245 | |||
246 | if args.option == 4: |
||
247 | print_stats(data, args.target, args.stats) |
||
248 | elif args.option == 5: |
||
249 | print_flow_stats(data, args.target) |
||
250 | elif args.option == 6: |
||
251 | print_tables_stats(data, args.target, args.stats) |
||
252 | elif args.target: |
||
253 | print_target_results(data, args.option, args.target) |
||
254 | elif args.count_output == 1: |
||
255 | # Count amount of items |
||
256 | print(len(data)) |
||
257 | elif args.count_output == 2: |
||
258 | # List in JSON format. Don't show status! |
||
259 | list_items(data, args.option) |
||
260 | else: |
||
261 | parser.print_help() |
||
262 |