riak 7.76 KB
Newer Older
James Martin committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
#!/usr/bin/python
# -*- coding: utf-8 -*-

# (c) 2013, James Martin <jmartin@basho.com>, Drew Kerrigan <dkerrigan@basho.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
#
DOCUMENTATION = '''
---
James Martin committed
23
module: riak
James Martin committed
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
short_description: This module handles some common Riak operations
description:
     - This module can be used to join nodes to a cluster, check
       the status of the cluster.
version_added: "1.2"
options:
  command:
    description:
      - The command you would like to perform against the cluster.
    required: false
    default: null
    aliases: []
    choices: ['ping', 'kv_test', 'join', 'plan', 'commit']
  config_dir:
    description:
      - The path to the riak configuration directory
    required: false
    default: /etc/riak
    aliases: []
  http_conn:
    description:
      - The ip address and port that is listening for Riak HTTP queries
    required: false
    default: 127.0.0.1:8098
    aliases: []
  target_node:
    description:
      - The target node for certain operations (join, ping)
    required: false
    default: riak@127.0.0.1
    aliases: []
  wait_for_handoffs:
    description:
57
      - Number of seconds to wait for handoffs to complete.
James Martin committed
58 59 60
    required: false
    default: null
    aliases: []
61
    type: 'int'
62 63
  wait_for_ring:
    description:
64
      - Number of seconds to wait for all nodes to agree on the ring.
65 66 67
    required: false
    default: null
    aliases: []
68
    type: 'int'
James Martin committed
69 70 71 72
  wait_for_service:
    description:
      - Waits for a riak service to come online before continuing.
    required: false
73
    default: None
James Martin committed
74 75 76 77
    aliases: []
    choices: ['kv']
'''

78 79 80 81 82 83 84 85 86 87
EXAMPLES = '''
# Join's a Riak node to another node
- riak: command=join target_node=riak@10.1.1.1

# Wait for handoffs to finish.  Use with async and poll.
- riak: wait_for_handoffs=yes

# Wait for riak_kv service to startup
- riak: wait_for_service=kv
'''
James Martin committed
88 89 90 91

import urllib2
import json
import time
92
import socket
James Martin committed
93

94 95 96 97
def ring_check(module, riak_admin_bin):
    cmd = '%s ringready 2> /dev/null' % riak_admin_bin
    rc, out, err = module.run_command(cmd)
    if rc == 0 and 'TRUE All nodes agree on the ring' in out:
98 99 100
        return True
    else:
        return False
James Martin committed
101 102 103

def main():

104 105
    module = AnsibleModule(
        argument_spec=dict(
James Martin committed
106 107 108 109 110
        command=dict(required=False, default=None, choices=[
                    'ping', 'kv_test', 'join', 'plan', 'commit']),
        config_dir=dict(default='/etc/riak'),
        http_conn=dict(required=False, default='127.0.0.1:8098'),
        target_node=dict(default='riak@127.0.0.1', required=False),
111 112
        wait_for_handoffs=dict(default=False, type='int'),
        wait_for_ring=dict(default=False, type='int'),
James Martin committed
113 114
        wait_for_service=dict(
            required=False, default=None, choices=['kv'])
115
        )
James Martin committed
116 117 118 119 120 121 122 123
    )


    command = module.params.get('command')
    config_dir = module.params.get('config_dir')
    http_conn = module.params.get('http_conn')
    target_node = module.params.get('target_node')
    wait_for_handoffs = module.params.get('wait_for_handoffs')
124
    wait_for_ring = module.params.get('wait_for_ring')
James Martin committed
125 126 127 128
    wait_for_service = module.params.get('wait_for_service')


    #make sure riak commands are on the path
129 130
    riak_bin = module.get_bin_path('riak')
    riak_admin_bin = module.get_bin_path('riak-admin')
James Martin committed
131

132 133
    cmd = "%s version 2> /dev/null |grep ^riak|cut -f2 -d' '|tr -d '('" % riak_bin
    rc, out, err = module.run_command(cmd)
James Martin committed
134 135 136 137 138
    if rc == 0:
        version = out.strip()
    else:
        module.fail_json(msg='Could not determine Riak version')

139 140 141
# here we attempt to get stats from the http stats interface for 120 seconds.
    timeout = time.time() + 120
    while True:
Michael DeHaan committed
142
        if time.time() > timeout:
143 144 145 146 147 148 149 150 151
            module.fail_json(msg='Timeout, could not fetch Riak stats.')
        try:
            stats_raw = urllib2.urlopen(
                'http://%s/stats' % (http_conn), None, 5).read()
            break
        except urllib2.HTTPError, e:
            time.sleep(5)
        except urllib2.URLError, e:
            time.sleep(5)
152 153
        except socket.timeout:
            time.sleep(5)
154 155 156 157
        except Exception, e:
            module.fail_json(msg='Could not fetch Riak stats: %s' % e)

# here we attempt to load those stats,
James Martin committed
158
    try:
159 160
        stats = json.loads(stats_raw)
    except:
161
        module.fail_json(msg='Could not parse Riak stats.')
James Martin committed
162 163 164 165 166 167

    node_name = stats['nodename']
    nodes = stats['ring_members']
    ring_size = stats['ring_creation_size']


168 169 170 171
    result = dict(node_name=node_name,
              nodes=nodes,
              ring_size=ring_size,
              version=version)
James Martin committed
172 173

    if command == 'ping':
174 175
        cmd = '%s ping %s' % ( riak_bin, target_node )
        rc, out, err = module.run_command(cmd)
James Martin committed
176 177 178 179 180 181
        if rc == 0:
            result['ping'] = out
        else:
            module.fail_json(msg=out)

    elif command == 'kv_test':
182 183
        cmd = '%s test' % riak_admin_bin
        rc, out, err = module.run_command(cmd)
James Martin committed
184 185 186 187 188 189 190 191 192
        if rc == 0:
            result['kv_test'] = out
        else:
            module.fail_json(msg=out)

    elif command == 'join':
        if nodes.count(node_name) == 1 and len(nodes) > 1:
            result['join'] = 'Node is already in cluster or staged to be in cluster.'
        else:
193 194
            cmd = '%s cluster join %s' % (riak_admin_bin, target_node)
            rc, out, err = module.run_command(cmd)
James Martin committed
195 196 197 198 199 200 201
            if rc == 0:
                result['join'] = out
                result['changed'] = True
            else:
                module.fail_json(msg=out)

    elif command == 'plan':
202 203
        cmd = '%s cluster plan' % riak_admin_bin
        rc, out, err = module.run_command(cmd)
James Martin committed
204 205
        if rc == 0:
            result['plan'] = out
206
            if 'Staged Changes' in out:
James Martin committed
207 208 209 210 211
                result['changed'] = True
        else:
            module.fail_json(msg=out)

    elif command == 'commit':
212 213
        cmd = '%s cluster commit' % riak_admin_bin
        rc, out, err = module.run_command(cmd)
James Martin committed
214 215
        if rc == 0:
            result['commit'] = out
216
            result['changed'] = True
James Martin committed
217 218 219 220 221
        else:
            module.fail_json(msg=out)

# this could take a while, recommend to run in async mode
    if wait_for_handoffs:
222 223
        timeout = time.time() + wait_for_handoffs
        while True:
224 225 226
            cmd = '%s transfers 2> /dev/null' % riak_admin_bin
            rc, out, err = module.run_command(cmd)
            if 'No transfers active' in out:
James Martin committed
227 228
                result['handoffs'] = 'No transfers active.'
                break
229
            time.sleep(10)
Michael DeHaan committed
230
            if time.time() > timeout:
231
                module.fail_json(msg='Timeout waiting for handoffs.')
James Martin committed
232 233

    if wait_for_service:
234 235
        cmd = '%s wait_for_service riak_%s %s' % ( riak_admin_bin, wait_for_service, node_name)
        rc, out, err = module.run_command(cmd)
James Martin committed
236 237
        result['service'] = out

238
    if wait_for_ring:
239 240
        timeout = time.time() + wait_for_ring
        while True:
241
            if ring_check(module, riak_admin_bin):
242 243
                break
            time.sleep(10)
Michael DeHaan committed
244
        if time.time() > timeout:
245
            module.fail_json(msg='Timeout waiting for nodes to agree on ring.')
246

247
    result['ring_ready'] = ring_check(module, riak_admin_bin)
James Martin committed
248 249 250 251 252 253 254

    module.exit_json(**result)

# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>

main()