~1chb1n/ubuntu-openstack-ci/mptest0

« back to all changes in this revision

Viewing changes to tools/juju_status_health_check.py

  • Committer: Ryan Beisner
  • Date: 2016-02-23 14:26:10 UTC
  • mfrom: (30.2.201 ubuntu-openstack-ci)
  • Revision ID: ryan.beisner@canonical.com-20160223142610-g4tajprej8p21o08
Rebase parent

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
'''
 
3
  juju status health check (not to be confused with juju health status)
 
4
 
 
5
  A quick health check based on juju agent states reported in juju stat, plus
 
6
  SSH and juju agent TCP port connection checks, and ping connectivity checks.
 
7
'''
 
8
import logging
 
9
import optparse
 
10
import os
 
11
import subprocess
 
12
import sys
 
13
import yaml
 
14
sys.path.insert(1, os.path.join(sys.path[0], '..'))
 
15
from common import osci_utils  # noqa
 
16
 
 
17
 
 
18
USAGE = '''Usage: %prog [options]
 
19
 
 
20
  juju status health check (not to be confused with juju health status)
 
21
 
 
22
  A quick health check based on juju agent states reported in juju stat, plus
 
23
  SSH and juju agent TCP port connection checks, and ping connectivity checks.
 
24
'''
 
25
 
 
26
 
 
27
def jshc_ssh_command_check(host='127.0.0.1'):
 
28
    ''' A locally reusable chunk to check SSH command success on a remote host.
 
29
    '''
 
30
 
 
31
    pkey = os.path.expanduser('~/.juju/ssh/juju_id_rsa')
 
32
 
 
33
    ret = osci_utils.func_timeout(timeout=10,
 
34
                                  func_call=osci_utils.ssh_command_check,
 
35
                                  kwargs={'host': host,
 
36
                                          'username': 'ubuntu',
 
37
                                          'pkey': pkey})
 
38
 
 
39
    if ret:
 
40
        logging.info('SSH command check OK for {}.'.format(host))
 
41
        return True, ''
 
42
    else:
 
43
        logging.warn('SSH command check FAIL for'
 
44
                     ' {}'.format(host))
 
45
    return False, 'host:  {}'.format(host)
 
46
 
 
47
 
 
48
def jshc_tcp_socket_check(host="127.0.0.1", port=22, timeout=15):
 
49
    ''' A locally reusable chunk to check TCP socket connectivity
 
50
        and give feedback.
 
51
    '''
 
52
    if osci_utils.port_knock(host=host, port=port, timeout=timeout):
 
53
        logging.info('Socket connect OK for {} port {}.'.format(host, port))
 
54
        return True, ''
 
55
    else:
 
56
        logging.warn('Socket connect FAIL for'
 
57
                     ' {} port {}'.format(host, port))
 
58
    return False, 'host:  {}'.format(host)
 
59
 
 
60
 
 
61
def jshc_ping_check(host, check_hist):
 
62
    ''' A locally reusable chunk to check host pingability and give feedback.
 
63
    '''
 
64
    check_str = 'ping: {}'.format(host)
 
65
    if check_str in check_hist:
 
66
        logging.info('Already checked  {}'.format(check_str))
 
67
        return None, None, check_str
 
68
    else:
 
69
        if osci_utils.ping_host(host=host):
 
70
            logging.info('Ping OK for:  {}'.format(host))
 
71
            return True, None, check_str
 
72
        else:
 
73
            logging.warn('Ping FAILED for:  {}'.format(host))
 
74
            err_str = 'host:  {}'.format(host)
 
75
        return False, err_str, check_str
 
76
 
 
77
 
 
78
def jshc_validate():
 
79
    ''' Misc validation.
 
80
    '''
 
81
    try:
 
82
        _jstat = osci_utils.juju_stat()
 
83
    except subprocess.CalledProcessError:
 
84
        logging.error('Could not get juju status.')
 
85
        sys.exit(1)
 
86
 
 
87
    if 'services' not in _jstat:
 
88
        sys.exit(2)
 
89
 
 
90
 
 
91
def juju_status_health_check():
 
92
    ''' Check juju agent, instance, machine status and connectivity.
 
93
    '''
 
94
 
 
95
    jshc_validate()
 
96
    j_stat = osci_utils.juju_stat()
 
97
 
 
98
    sick = []
 
99
    check_hist = set()
 
100
    found_bootstrap_node = False
 
101
 
 
102
    # per-service unit state check loop
 
103
    logging.debug('Checking agents per service.')
 
104
    for charm, services in j_stat['services'].iteritems():
 
105
        logging.info('=== Checking service: {} ==='.format(charm))
 
106
 
 
107
        # check for existing units
 
108
        if 'units' not in services.keys():
 
109
            logging.info('No unit(s) for {}'.format(charm))
 
110
            continue
 
111
 
 
112
        # per-unit check loop
 
113
        for unit, unit_data in services['units'].iteritems():
 
114
            check_str = 'agent: {}'.format(unit)
 
115
            if check_str in check_hist:
 
116
                logging.info('Already checked  {}'.format(check_str))
 
117
            else:
 
118
                check_hist.add(check_str)
 
119
                if unit_data['agent-state'] == 'started':
 
120
                    logging.info('Agent OK for unit: {}'.format(unit))
 
121
                elif unit_data['agent-state'] == 'down':
 
122
                    logging.warn('Agent down for unit: {}'.format(unit))
 
123
                    sick.append('unit:  {}'.format(unit))
 
124
                else:
 
125
                    logging.warn('Agent state unknown ({}) for unit: '
 
126
                                 '{}'.format(unit_data['agent-state'],
 
127
                                             unit))
 
128
                    sick.append('unit:  {}'.format(unit))
 
129
 
 
130
                # connectivity checks
 
131
            if 'public-address' in unit_data.keys():
 
132
                host = unit_data['public-address']
 
133
 
 
134
                # SSH port connect check
 
135
                check_str = 'ssh socket: {}'.format(host)
 
136
                if check_str in check_hist:
 
137
                    logging.info('Already checked  {}'.format(check_str))
 
138
                else:
 
139
                    check_hist.add(check_str)
 
140
                    ret = jshc_tcp_socket_check(host=host)
 
141
                    if not ret[0]:
 
142
                        sick.append(ret[1])
 
143
                        sick.append('unit:  {}'.format(unit))
 
144
 
 
145
                # SSH command check
 
146
                check_str = 'ssh cmd: {}'.format(host)
 
147
                if check_str in check_hist:
 
148
                    logging.info('Already checked  {}'.format(check_str))
 
149
                else:
 
150
                    check_hist.add(check_str)
 
151
                    ret = jshc_ssh_command_check(host=host)
 
152
                    if not ret[0]:
 
153
                        sick.append(ret[1])
 
154
                        sick.append('unit:  {}'.format(unit))
 
155
 
 
156
                # ping check
 
157
                ret = jshc_ping_check(host=host, check_hist=check_hist)
 
158
                if ret[2]:
 
159
                    check_hist.add(ret[2])
 
160
                if ret[0] is False:
 
161
                    sick.extend([ret[1], 'unit:  {}'.format(unit)])
 
162
            else:
 
163
                logging.info('No public-address for unit:  {}'.format(unit))
 
164
 
 
165
    # per-machine unit state check
 
166
    logging.debug('Checking agents per machine.')
 
167
    for mach, mach_data in j_stat['machines'].iteritems():
 
168
        logging.info('=== Checking machine: {} ==='.format(mach))
 
169
 
 
170
        # agent status check
 
171
        if 'agent-state' not in mach_data.keys():
 
172
            logging.warn('No agent-state reported for {}'.format(mach))
 
173
            sick.append('machine:  {}'.format(mach))
 
174
            continue
 
175
 
 
176
        check_hist.add('agent: {}'.format(mach))
 
177
        if mach_data['agent-state'] == 'started':
 
178
            logging.info('Agent OK for machine: {}'.format(mach))
 
179
        elif mach_data['agent-state'] == 'down':
 
180
            logging.warn('Agent down for machine: {}'.format(mach))
 
181
            sick.append('machine:  {}'.format(mach))
 
182
        else:
 
183
            logging.warn('Agent state unknown ({}) for '
 
184
                         'machine:  {}'.format(mach_data['agent-state'],
 
185
                                               mach))
 
186
            sick.append('machine:  {}'.format(mach))
 
187
 
 
188
        # bootstrap node check
 
189
        if 'state-server-member-status' in mach_data:
 
190
            if mach_data['state-server-member-status'] == 'has-vote':
 
191
                found_bootstrap_node = True
 
192
                logging.info('Found bootstrap machine: {}'.format(mach))
 
193
                if 'dns-name' in mach_data.keys():
 
194
                    host = mach_data['dns-name']
 
195
 
 
196
                    # juju agent port connect check
 
197
                    check_str = 'agent socket: {}'.format(host)
 
198
                    if check_str in check_hist:
 
199
                        logging.info('Already checked  {}'.format(check_str))
 
200
                    else:
 
201
                        check_hist.add(check_str)
 
202
                        ret = jshc_tcp_socket_check(host=host, port=17070)
 
203
                        if not ret[0]:
 
204
                            sick.append(ret[1])
 
205
                            sick.append('machine:  {}'.format(mach))
 
206
 
 
207
                    # SSH command check
 
208
                    check_str = 'ssh cmd: {}'.format(host)
 
209
                    if check_str in check_hist:
 
210
                        logging.info('Already checked  {}'.format(check_str))
 
211
                    else:
 
212
                        check_hist.add(check_str)
 
213
                        ret = jshc_ssh_command_check(host=host)
 
214
                        if not ret[0]:
 
215
                            sick.append(ret[1])
 
216
                            sick.append('machine:  {}'.format(mach))
 
217
 
 
218
        # connectivity checks
 
219
        if 'dns-name' in mach_data.keys():
 
220
            host = mach_data['dns-name']
 
221
 
 
222
            # SSH port connect check
 
223
            check_str = 'ssh socket: {}'.format(host)
 
224
            if check_str in check_hist:
 
225
                logging.info('Already checked  {}'.format(check_str))
 
226
            else:
 
227
                check_hist.add(check_str)
 
228
                ret = jshc_tcp_socket_check(host=host)
 
229
                if not ret[0]:
 
230
                    sick.append(ret[1])
 
231
                    sick.append('machine:  {}'.format(mach))
 
232
 
 
233
            # SSH command check
 
234
            check_str = 'ssh cmd: {}'.format(host)
 
235
            if check_str in check_hist:
 
236
                logging.info('Already checked  {}'.format(check_str))
 
237
            else:
 
238
                check_hist.add(check_str)
 
239
                ret = jshc_ssh_command_check(host=host)
 
240
                if not ret[0]:
 
241
                    sick.append(ret[1])
 
242
                    sick.append('machine:  {}'.format(mach))
 
243
 
 
244
            # ping check
 
245
            ret = jshc_ping_check(host=host, check_hist=check_hist)
 
246
            if ret[2]:
 
247
                check_hist.add(ret[2])
 
248
            if ret[0] is False:
 
249
                sick.extend([ret[1], 'machine:  {}'.format(unit)])
 
250
 
 
251
        # per-machine container state check
 
252
        if 'containers' in mach_data:
 
253
            for container in mach_data['containers']:
 
254
                check_hist.add('agent: {}'.format(container))
 
255
                if mach_data['agent-state'] == 'started':
 
256
                    logging.info('Agent OK for container: '
 
257
                                 '{}'.format(container))
 
258
                elif mach_data['agent-state'] == 'down':
 
259
                    logging.warn('Agent down for container:'
 
260
                                 ' {}'.format(container))
 
261
                    sick.append('container:  {}'.format(container))
 
262
                else:
 
263
                    logging.warn('Agent state unknown ({}) for container: '
 
264
                                 '{}'.format(mach_data['agent-state'],
 
265
                                             container))
 
266
                    sick.append('container:  {}'.format(container))
 
267
 
 
268
                if 'dns-name' in mach_data['containers'][container]:
 
269
                    host = mach_data['containers'][container]['dns-name']
 
270
 
 
271
                    # SSH port connect check
 
272
                    check_str = 'ssh socket: {}'.format(host)
 
273
                    if check_str in check_hist:
 
274
                        logging.info('Already checked  {}'.format(check_str))
 
275
                    else:
 
276
                        check_hist.add(check_str)
 
277
                        ret = jshc_tcp_socket_check(host=host)
 
278
                        if not ret[0]:
 
279
                            sick.append(ret[1])
 
280
                            sick.append('container: '.format(container))
 
281
 
 
282
                    # SSH command check
 
283
                    check_str = 'ssh cmd: {}'.format(host)
 
284
                    if check_str in check_hist:
 
285
                        logging.info('Already checked  {}'.format(check_str))
 
286
                    else:
 
287
                        check_hist.add(check_str)
 
288
                        ret = jshc_ssh_command_check(host=host)
 
289
                        if not ret[0]:
 
290
                            sick.append(ret[1])
 
291
                            sick.append('container:  {}'.format(container))
 
292
 
 
293
                    # ping check
 
294
                    ret = jshc_ping_check(host=host, check_hist=check_hist)
 
295
                    if ret[2]:
 
296
                        check_hist.add(ret[2])
 
297
                    if ret[0] is False:
 
298
                        sick.extend([ret[1], 'container:  {}'.format(unit)])
 
299
 
 
300
    # check results
 
301
    logging.debug('Check history: {}'.format(check_hist))
 
302
    if not found_bootstrap_node:
 
303
        sick.append('bootstrap node (not identified)')
 
304
 
 
305
    logging.debug('Sick data: {}'.format(sick))
 
306
    if sick:
 
307
        sick.sort()
 
308
        logging.error('One or more units are not ok:'
 
309
                      '\n    {}'.format('\n    '.join(list(set(sick)))))
 
310
        sys.exit(1)
 
311
    else:
 
312
        logging.info('  OK: All units and machines have agents '
 
313
                     'running and they are reachable.')
 
314
 
 
315
 
 
316
def main():
 
317
    '''Define and handle command line parameters
 
318
    '''
 
319
    # Define command line options
 
320
    parser = optparse.OptionParser(USAGE)
 
321
    parser.add_option("-d", "--debug",
 
322
                      help="Enable debug output",
 
323
                      dest="debug", action="store_true", default=False)
 
324
 
 
325
    parser.add_option("-q", "--quiet",
 
326
                      help="Less output (WARN and ERROR only)",
 
327
                      dest="quiet", action="store_true", default=False)
 
328
 
 
329
    params = parser.parse_args()
 
330
    (opts, args) = params
 
331
 
 
332
    # Handle parameters, inform user
 
333
    if opts.debug and not opts.quiet:
 
334
        logging.basicConfig(level=logging.DEBUG)
 
335
        logging.info('Logging level set to DEBUG!')
 
336
        logging.debug('parse opts: \n{}'.format(
 
337
            yaml.dump(vars(opts), default_flow_style=False)))
 
338
        logging.debug('parse args: {}'.format(args))
 
339
    elif opts.quiet:
 
340
        logging.basicConfig(level=logging.WARN)
 
341
    else:
 
342
        logging.basicConfig(level=logging.INFO)
 
343
 
 
344
    logging.info('Ubuntu OSCI Juju Status Health Check')
 
345
    juju_status_health_check()
 
346
 
 
347
 
 
348
if __name__ == '__main__':
 
349
    main()