wait_for 8.98 KB
Newer Older
1
#!/usr/bin/python
Jeroen Hoekx committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
# -*- coding: utf-8 -*-

# (c) 2012, Jeroen Hoekx <jeroen@hoekx.be>
#
# 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/>.

import socket
import datetime
import time
import sys
25
import re
Jeroen Hoekx committed
26

27 28 29
DOCUMENTATION = '''
---
module: wait_for
30
short_description: Waits for a condition before continuing.
31
description:
32 33 34 35
     - Waiting for a port to become available is useful for when services 
       are not immediately available after their init scripts return - 
       which is true of certain Java application servers. It is also 
       useful when starting guests with the M(virt) module and
36 37 38
       needing to pause until they are ready. 
     - This module can also be used to wait for a regex match a string to be present in a file.
     - In 1.6 and later, this module can 
39
       also be used to wait for a file to be available or absent on the 
40
       filesystem.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
version_added: "0.7"
options:
  host:
    description:
      - hostname or IP address to wait for
    required: false
    default: "127.0.0.1"
    aliases: []
  timeout:
    description:
      - maximum number of seconds to wait for
    required: false
    default: 300
  delay:
    description:
      - number of seconds to wait before starting to poll
    required: false
    default: 0
  port:
    description:
      - port number to poll
62
    required: false
63 64
  state:
    description:
65
      - either C(present), C(started), or C(stopped), C(absent)
66
      - When checking a port C(started) will ensure the port is open, C(stopped) will check that it is closed
67 68
      - When checking for a file or a search string C(present) or C(started) will ensure that the file or string is present before continuing, C(absent) will check that file is absent or removed
    choices: [ "present", "started", "stopped", "absent" ]
69
    default: "started"
70 71 72 73 74 75 76 77 78
  path:
    version_added: "1.4"
    required: false
    description:
      - path to a file on the filesytem that must exist before continuing
  search_regex:
    version_added: "1.4"
    required: false
    description:
79 80 81
      - Can be used to match a string in either a file or a socket connection. Defaults to a multiline regex.
notes:
  - The ability to use search_regex with a port connection was added in 1.7.
82
requirements: []
83
author: Jeroen Hoekx, John Jarvis, Andrii Radyk
84 85
'''

Michael DeHaan committed
86
EXAMPLES = '''
87

Michael DeHaan committed
88
# wait 300 seconds for port 8000 to become open on the host, don't start checking for 10 seconds
89
- wait_for: port=8000 delay=10
90 91 92 93 94 95 96

# wait until the file /tmp/foo is present before continuing
- wait_for: path=/tmp/foo

# wait until the string "completed" is in the file /tmp/foo before continuing
- wait_for: path=/tmp/foo search_regex=completed

97 98 99 100 101 102
# wait until the lock file is removed
- wait_for: path=/var/lock/file.lock state=absent 

# wait until the process is finished and pid was destroyed
- wait_for: path=/proc/3466/status state=absent

103 104 105
# Wait 300 seconds for port 22 to become open and contain "OpenSSH", don't start checking for 10 seconds
- local_action: wait_for port=22 host="{{ inventory_hostname }}" search_regex=OpenSSH delay=10

Michael DeHaan committed
106 107
'''

Jeroen Hoekx committed
108 109 110 111
def main():

    module = AnsibleModule(
        argument_spec = dict(
112
            host=dict(default='127.0.0.1'),
Jeroen Hoekx committed
113
            timeout=dict(default=300),
114
            connect_timeout=dict(default=5),
115
            delay=dict(default=0),
116 117 118
            port=dict(default=None),
            path=dict(default=None),
            search_regex=dict(default=None),
119
            state=dict(default='started', choices=['started', 'stopped', 'present', 'absent']),
Jeroen Hoekx committed
120 121 122 123 124
        ),
    )

    params = module.params

125
    host = params['host']
Jeroen Hoekx committed
126
    timeout = int(params['timeout'])
127
    connect_timeout = int(params['connect_timeout'])
128
    delay = int(params['delay'])
129 130 131 132
    if params['port']:
        port = int(params['port'])
    else:
        port = None
133
    state = params['state']
134 135 136 137 138 139 140 141
    path = params['path']
    search_regex = params['search_regex']
    
    if port and path:
        module.fail_json(msg="port and path parameter can not both be passed to wait_for")
    if path and state == 'stopped':
        module.fail_json(msg="state=stopped should only be used for checking a port in the wait_for module")
        
142 143
    start = datetime.datetime.now()

144 145 146
    if delay:
        time.sleep(delay)

147
    if state in [ 'stopped', 'absent' ]:
148
        ### first wait for the stop condition
149
        end = start + datetime.timedelta(seconds=timeout)
Jeroen Hoekx committed
150

151
        while datetime.datetime.now() < end:
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
            if path:
                try:
                    f = open(path)
                    f.close()
                    time.sleep(1)
                    pass
                except IOError:
                    break
            elif port:
                s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                s.settimeout(connect_timeout)
                try:
                    s.connect( (host, port) )
                    s.shutdown(socket.SHUT_RDWR)
                    s.close()
                    time.sleep(1)
                except:
                    break
170
        else:
171
            elapsed = datetime.datetime.now() - start
172 173 174 175
            if port:
                module.fail_json(msg="Timeout when waiting for %s:%s to stop." % (host, port), elapsed=elapsed.seconds)
            elif path:
                module.fail_json(msg="Timeout when waiting for %s to be absent." % (path), elapsed=elapsed.seconds)
Jeroen Hoekx committed
176

177 178
    elif state in ['started', 'present']:
        ### wait for start condition
179
        end = start + datetime.timedelta(seconds=timeout)
180
        while datetime.datetime.now() < end:
181
            if path:
182 183
                try:
                    os.stat(path)
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
                    if search_regex:
                        try:
                            f = open(path)
                            try:
                                if re.search(search_regex, f.read(), re.MULTILINE):
                                    break
                                else:
                                    time.sleep(1)
                            finally:
                                f.close()
                        except IOError:
                            time.sleep(1)
                            pass
                    else:
                        break
199 200
                except OSError, e:
                    # File not present
201
                    if e.errno == 2:
202 203 204 205
                        time.sleep(1)
                    else:
                        elapsed = datetime.datetime.now() - start
                        module.fail_json(msg="Failed to stat %s, %s" % (path, e.strerror), elapsed=elapsed.seconds)
206 207 208 209 210
            elif port:
                s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                s.settimeout(connect_timeout)
                try:
                    s.connect( (host, port) )
211 212 213 214 215
                    if search_regex:
                        data = ''
                        matched = False
                        while 1:
                            data += s.recv(1024)
216 217 218
                            if not data:
                                break
                            elif re.search(search_regex, data, re.MULTILINE):
219 220 221 222 223 224 225 226 227 228
                                matched = True
                                break
                        if matched:
                            s.shutdown(socket.SHUT_RDWR)
                            s.close()
                            break
                    else:
                        s.shutdown(socket.SHUT_RDWR)
                        s.close()
                        break
229 230 231
                except:
                    time.sleep(1)
                    pass
232
        else:
233
            elapsed = datetime.datetime.now() - start
234
            if port:
235 236 237 238
                if search_regex:
                    module.fail_json(msg="Timeout when waiting for search string %s in %s:%s" % (search_regex, host, port), elapsed=elapsed.seconds)
                else:
                    module.fail_json(msg="Timeout when waiting for %s:%s" % (host, port), elapsed=elapsed.seconds)
239 240 241 242 243 244
            elif path:
                if search_regex:
                    module.fail_json(msg="Timeout when waiting for search string %s in %s" % (search_regex, path), elapsed=elapsed.seconds)
                else:
                    module.fail_json(msg="Timeout when waiting for file %s" % (path), elapsed=elapsed.seconds)

245

246
    elapsed = datetime.datetime.now() - start
247
    module.exit_json(state=state, port=port, search_regex=search_regex, path=path, elapsed=elapsed.seconds)
Jeroen Hoekx committed
248

249
# import module snippets
250
from ansible.module_utils.basic import *
Jeroen Hoekx committed
251
main()