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