3 """ Representation for connd state as returned by tell-connd --status
5 Christian Herdtweck, Intra2net, January 2015
9 from __future__ import print_function
11 from re import match as regexp
15 DEFAULT_TELL_CONND_BINARY = '/usr/intranator/bin/tell-connd'
18 ONLINE_STATE_ALWAYS_ONLINE = 'always online'
19 ONLINE_STATE_ALWAYS_OFFLINE = 'always offline'
20 ONLINE_STATE_DIAL_ON_COMMAND = 'dial on command'
21 ONLINE_STATE_DIAL_ON_DEMAND = 'dial on demand'
24 SUBSYS_DYNDNS = 'dyndns'
27 SUBSYS_SOCKS = 'socks'
29 SUBSYS_WEBPROXY = 'webproxy'
30 SUBSYS_PINGCHECK = 'pingcheck'
31 SUBSYS_IPONLINE = 'iponline'
32 ALL_SUBSYS = (SUBSYS_DNS, SUBSYS_DYNDNS, SUBSYS_MAIL, SUBSYS_NTP,
33 SUBSYS_SOCKS, SUBSYS_VPN, SUBSYS_WEBPROXY, SUBSYS_PINGCHECK,
36 ALL_STATES = (ONLINE_STATE_DIAL_ON_DEMAND, ONLINE_STATE_DIAL_ON_COMMAND,
37 ONLINE_STATE_ALWAYS_OFFLINE, ONLINE_STATE_ALWAYS_ONLINE)
40 class ConndState(object):
41 """ representation of connd's status as returned by tell-connd --status """
44 default_provider = None
47 subsys_disabled = None
57 '[ConndState: {0} (default {1}), {2} conn\'s, {3} ips, {4} vpns ]'\
58 .format(self.online_mode, self.default_provider,
59 len(self.connections), len(self.online_ips),
60 len(self.connected_vpns))
62 def complete_str(self):
63 """ return a string representating the complete state """
67 'ConndState: online mode = "{0}" (default provider: {1})\n'
68 .format(self.online_mode, self.default_provider), ]
71 # ' connctns: (repeated here for correct aligning)
72 parts.append(' subsys: online: ')
73 if self.subsys_online:
74 for subsys in self.subsys_online:
75 parts.append(subsys + ' ')
78 parts.append('; offline: ')
79 if self.subsys_offline:
80 for subsys in self.subsys_offline:
81 parts.append(subsys + ' ')
84 parts.append('; disabled: ')
85 if self.subsys_disabled:
86 for subsys in self.subsys_disabled:
87 parts.append(subsys + ' ')
93 parts.append(' conns: ')
95 name, info, actions = self.connections[0]
96 parts.append('{0}: {1}, {2}\n'.format(name, info, actions))
98 parts.append('None\n')
99 for name, info, actions in self.connections[1:]:
100 # ' connctns: (repeated here for correct aligning)
101 parts.append(' {0}: {1}, {2}\n'.format(name, info,
105 # ' connctns: (repeated here for correct aligning)
106 parts.append(' actions: ')
108 parts.append(self.actions[0] + '\n')
110 parts.append('None\n')
111 for action in self.actions[1:]:
112 # ' connctns: (repeated here for correct aligning)
113 parts.append(' {0}\n'.format(action))
116 # ' connctns: (repeated here for correct aligning)
117 parts.append(' IPs: ')
119 parts.append(self.online_ips[0])
120 for curr_ip in self.online_ips[1:]:
121 parts.append(', {0}'.format(curr_ip))
127 # ' connctns: (repeated here for correct aligning)
128 parts.append(' VPNs: ')
129 if self.connected_vpns:
130 parts.append(self.connected_vpns[0])
131 for vpn in self.connected_vpns[1:]:
132 parts.append(', {0}'.format(vpn))
137 # log level and target:
138 # ' connctns: (repeated here for correct aligning)
139 parts.append(' Log: level {0}'.format(self.log_level))
141 parts.append(' to {0}'.format(self.log_file))
144 return ''.join(parts)
145 # end: ConndState.complete_str
148 def run_tell_connd(tell_connd_binary=DEFAULT_TELL_CONND_BINARY):
149 """ run tell-connd --status, return output iterator and return code
151 catches all it can, so should usually return (output, return_code)
152 where output = [line1, line2, ...]
153 if return_code != 0, output's first line(s) is error message
156 output = subprocess.check_output(
157 [tell_connd_binary, '--status'], stderr=subprocess.STDOUT,
158 universal_newlines=True, shell=False, timeout=TIMEOUT)
159 return EX_OK, output.splitlines()
160 except subprocess.CalledProcessError as cpe: # non-zero return status
162 'tell-connd exited with status {0}'.format(cpe.returncode), ]
163 output.extend(cpe.output.splitlines())
164 return cpe.returncode, output
165 # not python-2-compatible:
166 # except subprocess.TimeoutExpired as texp:
168 # 'tell-connd timed out after {0}s. Returning -1'.format(
170 # output.extend(te.output.splitlines())
172 except Exception as exp:
173 output = [str(exp), ]
175 # end: ConndState.run_tell_connd
178 def get_state(tell_connd_binary=DEFAULT_TELL_CONND_BINARY):
179 """ get actual state from tell-connd --status
181 returns (err_code, output_lines) if something goes wrong running
182 binary; raises assertion if output from tell-connd does not match
188 err_code, all_lines = ConndState.run_tell_connd(tell_connd_binary)
189 if err_code != EX_OK:
190 return err_code, all_lines
192 output = iter(all_lines)
195 line = next(output).strip()
196 state.online_mode = regexp('online mode\s*:\s*(.+)$', line).groups()[0]
197 assert state.online_mode in ALL_STATES, \
198 'unexpected online mode: {0}'.format(state.online_mode)
200 line = next(output).strip()
201 state.default_provider = regexp('default provider\s*:\s*(.*)$',
203 if len(state.default_provider) == 0:
204 state.default_provider = None
205 line = next(output).strip()
206 assert len(line) == 0, 'expected empty line, but got {0}'.format(line)
209 line = next(output).strip()
210 assert line == 'subsys', 'expected subsys but got {0}'.format(line)
211 line = next(output).strip()
212 state.subsys_online = regexp('online\s*:\s*(.*)$', line)\
214 for subsys in state.subsys_online:
215 assert subsys in ALL_SUBSYS, \
216 'unexpected subsys: {0}'.format(subsys)
217 line = next(output).strip()
218 state.subsys_offline = regexp('offline\s*:\s*(.*)$', line)\
220 for subsys in state.subsys_offline:
221 assert subsys in ALL_SUBSYS, \
222 'unexpected subsys: {0}'.format(subsys)
223 line = next(output).strip()
224 state.subsys_disabled = regexp('disabled\s*:\s*(.*)$', line)\
226 for subsys in state.subsys_disabled:
227 assert subsys in ALL_SUBSYS, \
228 'unexpected subsys: {0}'.format(subsys)
229 line = next(output).strip()
230 assert len(line) == 0, 'expected empty line, but got {0}'.format(line)
233 state.connections = []
234 line = next(output).strip()
235 assert line == 'connection map:', \
236 'expected connection map but got {0}'.format(line)
243 if line == 'end of connection map':
245 conn_name, conn_info = regexp(
246 '\[\s*(.+)\s*\]\s*:\s*\(\s*(.*)\s*\)', line).groups()
249 conn_actions = regexp('actions\s*:\s*\[\s*(.+)\s*\]', line)\
251 state.connections.append((conn_name, conn_info, conn_actions))
255 line = next(output).strip()
256 assert len(line) == 0, 'expected empty line, but got {0}'.format(line)
259 line = next(output).strip()
260 state.actions = regexp('actions\s*:\s*(.*)', line).groups()[0].split()
261 if len(state.actions) == 1 and state.actions[0].strip() == '-':
263 line = next(output).strip()
264 assert len(line) == 0, 'expected empty line, but got {0}'.format(line)
267 line = next(output).strip()
268 state.online_ips = regexp('list of online ips\s*:\s*(.*)', line)\
270 if len(state.online_ips) == 1 \
271 and state.online_ips[0].strip() == 'NONE':
272 state.online_ips = []
273 line = next(output).strip()
274 assert len(line) == 0, 'expected empty line, but got {0}'.format(line)
277 state.connected_vpns = []
278 line = next(output).strip()
279 assert line == 'vpns connected:', \
280 'expected vpns connected, got {0}'.format(line)
285 elif line == 'end of list of connected vpns':
288 state.connected_vpns.append(line)
290 line = next(output).strip()
291 assert len(line) == 0, 'expected empty line, but got {0}'.format(line)
294 line = next(output).strip()
295 state.log_level, state.log_file = \
296 regexp('Logging with level (.+)(?:\s+to\s+(.+))?', line).groups()
299 line = next(output).strip()
300 assert len(line) == 0, 'expected empty line, but got {0}'.format(line)
301 line = next(output).strip()
302 assert line == 'Done.', 'expect Done but got {0}'.format(line)
305 # end: ConndState.get_state
307 # end: class ConndState
311 """ get state and print it """
312 state = ConndState.get_state()
314 print(state.complete_str())
318 """ Main function, called when running file as script; runs test() """
323 if __name__ == '__main__':