wait_for 7.92 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 79 80
  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:
      - with the path option can be used match a string in the file that must match before continuing.  Defaults to a multiline regex.
   
81
notes: []
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

Michael DeHaan committed
103 104
'''

Jeroen Hoekx committed
105 106 107 108
def main():

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

    params = module.params

122
    host = params['host']
Jeroen Hoekx committed
123
    timeout = int(params['timeout'])
124
    connect_timeout = int(params['connect_timeout'])
125
    delay = int(params['delay'])
126 127 128 129
    if params['port']:
        port = int(params['port'])
    else:
        port = None
130
    state = params['state']
131 132 133 134 135 136 137 138
    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")
        
139 140
    start = datetime.datetime.now()

141 142 143
    if delay:
        time.sleep(delay)

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

148
        while datetime.datetime.now() < end:
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
            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
167
        else:
168
            elapsed = datetime.datetime.now() - start
169 170 171 172
            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
173

174 175
    elif state in ['started', 'present']:
        ### wait for start condition
176
        end = start + datetime.timedelta(seconds=timeout)
177
        while datetime.datetime.now() < end:
178
            if path:
179 180
                try:
                    os.stat(path)
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
                    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
196 197 198 199 200 201 202
                except OSError, e:
                    # File not present
                    if os.errno == 2:
                        time.sleep(1)
                    else:
                        elapsed = datetime.datetime.now() - start
                        module.fail_json(msg="Failed to stat %s, %s" % (path, e.strerror), elapsed=elapsed.seconds)
203 204 205 206 207 208 209 210 211 212 213
            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()
                    break
                except:
                    time.sleep(1)
                    pass
214
        else:
215
            elapsed = datetime.datetime.now() - start
216 217 218 219 220 221 222 223
            if port:
                module.fail_json(msg="Timeout when waiting for %s:%s" % (host, port), elapsed=elapsed.seconds)
            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)

224

225
    elapsed = datetime.datetime.now() - start
226
    module.exit_json(state=state, port=port, search_regex=search_regex, path=path, elapsed=elapsed.seconds)
Jeroen Hoekx committed
227

228
# import module snippets
229
from ansible.module_utils.basic import *
Jeroen Hoekx committed
230
main()