ca96011157a25a657b7a194aaff8debcf359e9a6
[pingcheck] / test / connd_state.py
1 #!/usr/bin/env python3
2
3 """ Representation for connd state as returned by tell-connd --status
4
5 Christian Herdtweck, Intra2net, January 2015
6 (c) Intra2net AG 2015
7 """
8
9 # Version History
10 # 16/01/15 Christian Herdtweck: started creation
11
12 import subprocess
13 from re import match as regexp
14 from os import EX_OK
15
16 # constants
17 default_tell_connd_binary = '/usr/intranator/bin/tell-connd'
18 timeout = 1
19
20 ONLINE_STATE_ALWAYS_ONLINE = 'always online'
21 ONLINE_STATE_ALWAYS_OFFLINE = 'always offline'
22 ONLINE_STATE_DIAL_ON_COMMAND = 'dial on command'
23 ONLINE_STATE_DIAL_ON_DEMAND = 'dial on demand'
24
25 SUBSYS_DNS       = 'dns'
26 SUBSYS_DYNDNS    = 'dyndns'
27 SUBSYS_MAIL      = 'mail'
28 SUBSYS_NTP       = 'ntp'
29 SUBSYS_SOCKS     = 'socks'
30 SUBSYS_VPN       = 'vpn'
31 SUBSYS_WEBPROXY  = 'webproxy'
32 SUBSYS_PINGCHECK = 'pingcheck'
33 ALL_SUBSYS = (SUBSYS_DNS, SUBSYS_DYNDNS, SUBSYS_MAIL, SUBSYS_NTP, \
34               SUBSYS_SOCKS, SUBSYS_VPN, SUBSYS_WEBPROXY, SUBSYS_PINGCHECK)
35
36 class ConndState:
37     """ representation of connd's status as returned by tell-connd --status """
38
39     online_mode = None
40     default_provider = None
41     subsys_online = None
42     subsys_offline = None
43     subsys_disabled = None
44     connections = None
45     actions = None
46     online_ips = None
47     connected_vpns = None
48
49     def __str__(self):
50         return '[ConndState: {0} (default {1}), {2} conn\'s, {3} ips, {4} vpns ]'.format(\
51                 self.online_mode, self.default_provider, len(self.connections), \
52                 len(self.online_ips), len(self.connected_vpns))
53
54     def complete_str(self):
55         # general
56         parts = ['ConndState: online mode = "{0}" (default provider: {1})\n'.format(\
57                   self.online_mode, self.default_provider), ]
58
59         # subsys
60         #            '  connctns:
61         parts.append('    subsys: online: ')
62         if self.subsys_online:
63             for subsys in self.subsys_online:
64                 parts.append(subsys + ' ')
65         else:
66             parts.append('None ')
67         parts.append(';  offline: ')
68         if self.subsys_offline:
69             for subsys in self.subsys_offline:
70                 parts.append(subsys + ' ')
71         else:
72             parts.append('None ')
73         parts.append(';  disabled: ')
74         if self.subsys_disabled:
75             for subsys in self.subsys_disabled:
76                 parts.append(subsys + ' ')
77         else:
78             parts.append('None')
79         parts.append('\n')
80
81         # connections
82         parts.append('     conns: ')
83         if self.connections:
84             name, info, actions = self.connections[0]
85             parts.append('{0}: {1}, {2}\n'.format(name, info, actions))
86         else:
87             parts.append('None\n')
88         for name, info, actions in self.connections[1:]:
89             #            '  connctns:
90             parts.append('            {0}: {1}, {2}\n'.format(name, info, actions))
91
92         # actions
93         #            '  connctns:
94         parts.append('   actions: ')
95         if self.actions:
96             parts.append(self.actions[0] + '\n')
97         else:
98             parts.append('None\n')
99         for action in self.actions[1:]:
100             #            '  connctns:
101             parts.append('            {0}\n'.format(action))
102
103         # online IPs
104         #            '  connctns:
105         parts.append('       IPs: ')
106         if self.online_ips:
107             parts.append(self.online_ips[0])
108             for ip in self.online_ips[1:]:
109                 parts.append(', {0}'.format(ip))
110         else:
111             parts.append('None')
112         parts.append('\n')
113
114         # VPNs
115         #            '  connctns:
116         parts.append('      VPNs: ')
117         if self.connected_vpns:
118             parts.append(self.connected_vpns[0])
119             for vpn in self.connected_vpns[1:]:
120                 parts.append(', {0}'.format(vpn))
121         else:
122             parts.append('None')
123         parts.append('\n')
124
125
126         return ''.join(parts)
127     #end: ConndState.complete_str
128
129
130     @staticmethod
131     def run_tell_connd(tell_connd_binary=default_tell_connd_binary):
132         """ run tell-connd --status, return output iterator and return code
133
134         catches all it can, so should usually return (output, return_code)
135           where output = [line1, line2, ...]
136         if return_code != 0, output's first line(s) is error message
137         """
138         try:
139             output = subprocess.check_output([tell_connd_binary, '--status'], \
140                     stderr=subprocess.STDOUT, universal_newlines=True, shell=False, timeout=timeout)
141             return EX_OK, output.splitlines()
142         except subprocess.CalledProcessError as cpe: # non-zero return status
143             output = ['tell-connd exited with status {0}'.format(cpe.returncode), ]
144             output.extend( cpe.output.splitlines() )
145             return cpe.returncode, output
146         except subprocess.TimeoutExpired as te:
147             output = ['tell-connd timed out after {0}s. Returning -1'.format(te.timeout), ]
148             output.extend( te.output.splitlines() )
149             return -1, output
150         except Exception as e:
151             output = [str(e),]
152             return -1, output
153     #end: ConndState.run_tell_connd
154
155
156     @staticmethod
157     def get_state(tell_connd_binary=default_tell_connd_binary):
158         """ get actual state from tell-connd --status
159
160         returns (err_code, output_lines) if something goes wrong running binary;
161         raises assertion if output from tell-connd does not match expected format
162         """
163
164         state = ConndState()
165
166         err_code, all_lines = ConndState.run_tell_connd(tell_connd_binary)
167         if err_code != EX_OK:
168             return err_code, all_lines
169
170         output = iter(all_lines)
171
172         # first section
173         line = next(output).strip()
174         state.online_mode = regexp('online mode\s*:\s*(.+)$', line).groups()[0]
175         assert( state.online_mode in (ONLINE_STATE_DIAL_ON_DEMAND, ONLINE_STATE_DIAL_ON_COMMAND, \
176                                       ONLINE_STATE_ALWAYS_OFFLINE, ONLINE_STATE_ALWAYS_ONLINE) )
177         line = next(output).strip()
178         state.default_provider = regexp('default provider\s*:\s*(.*)$', \
179                                         line).groups()[0]
180         if len(state.default_provider) == 0:
181             state.default_provider = None
182         line = next(output).strip()
183         assert( len(line) == 0 )
184
185         # subsys
186         line = next(output).strip()
187         assert( line == 'subsys' )
188         line = next(output).strip()
189         state.subsys_online   = regexp(  'online\s*:\s*(.*)$', line).groups()[0].split()
190         for subsys in state.subsys_online:
191             assert(subsys in ALL_SUBSYS)
192         line = next(output).strip()
193         state.subsys_offline  = regexp( 'offline\s*:\s*(.*)$', line).groups()[0].split()
194         for subsys in state.subsys_offline:
195             assert(subsys in ALL_SUBSYS)
196         line = next(output).strip()
197         state.subsys_disabled = regexp('disabled\s*:\s*(.*)$', line).groups()[0].split()
198         for subsys in state.subsys_disabled:
199             assert(subsys in ALL_SUBSYS)
200         line = next(output).strip()
201         assert( len(line) == 0 )
202
203         # connection map
204         state.connections = []
205         line = next(output).strip()
206         assert( line == 'connection map:' )
207         expect_new = True
208         for line in output:
209             line = line.strip()
210             if len(line) == 0:
211                 continue
212             if expect_new:
213                 if line == 'end of connection map':
214                     break
215                 conn_name, conn_info = regexp('\[\s*(.+)\s*\]\s*:\s*\(\s*(.*)\s*\)', line).groups()
216                 expect_new = False
217             else:
218                 conn_actions = regexp('actions\s*:\s*\[\s*(.+)\s*\]', line).groups()
219                 state.connections.append( (conn_name, conn_info, conn_actions) )
220                 expect_new = True
221         #end: for lines
222         assert( expect_new )
223         line = next(output).strip()
224         assert( len(line) == 0 )
225
226         # actions
227         line = next(output).strip()
228         state.actions = regexp('actions\s*:\s*(.*)', line).groups()[0].split()
229         if len(state.actions) == 1 and state.actions[0].strip() == '-':
230             state.actions = []
231         line = next(output).strip()
232         assert( len(line) == 0 )
233
234         # online IPs
235         line = next(output).strip()
236         state.online_ips = regexp('list of online ips\s*:\s*(.*)', \
237                                   line).groups()[0].split()
238         if len(state.online_ips) == 1 and state.online_ips[0].strip() == 'NONE':
239             state.online_ips = []
240         line = next(output).strip()
241         assert( len(line) == 0 )
242
243         # VPNs
244         state.connected_vpns = []
245         line = next(output).strip()
246         assert( line == 'vpns connected:' )
247         for line in output:
248             line = line.strip()
249             if len(line) == 0:
250                 continue
251             elif line == 'end of list of connected vpns':
252                 break
253             else:
254                 state.connected_vpns.append(line)
255         #end: for lines
256
257         # done
258         line = next(output).strip()
259         assert( len(line) == 0 )
260         line = next(output).strip()
261         assert( line == 'Done.' )
262
263         return state
264     #end: ConndState.get_state
265
266 #end: class ConndState
267
268
269 def test():
270     state = ConndState.get_state()
271     print(state)
272     print(state.complete_str())
273
274 def main():
275     """ Main function, called when running file as script; runs test()
276     """
277     test()
278 #end: function main
279
280
281 if __name__ == '__main__':
282     main()
283