2. Adjust comment on sources list from depends 3. For the /etc/timezone 'writing', add a header that says created by cloud-init
		
			
				
	
	
		
			227 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			227 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# vi: ts=4 expandtab
 | 
						|
#
 | 
						|
#    Copyright (C) 2012 Canonical Ltd.
 | 
						|
#    Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
 | 
						|
#    Copyright (C) 2012 Yahoo! Inc.
 | 
						|
#
 | 
						|
#    Author: Scott Moser <scott.moser@canonical.com>
 | 
						|
#    Author: Juerg Haefliger <juerg.haefliger@hp.com>
 | 
						|
#    Author: Joshua Harlow <harlowja@yahoo-inc.com>
 | 
						|
#
 | 
						|
#    This program is free software: you can redistribute it and/or modify
 | 
						|
#    it under the terms of the GNU General Public License version 3, as
 | 
						|
#    published by the Free Software Foundation.
 | 
						|
#
 | 
						|
#    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
from contextlib import closing
 | 
						|
 | 
						|
import errno
 | 
						|
import socket
 | 
						|
import time
 | 
						|
import urllib
 | 
						|
import urllib2
 | 
						|
 | 
						|
from cloudinit import log as logging
 | 
						|
from cloudinit import version
 | 
						|
 | 
						|
LOG = logging.getLogger(__name__)
 | 
						|
 | 
						|
 | 
						|
class UrlResponse(object):
 | 
						|
    def __init__(self, status_code, contents=None, headers=None):
 | 
						|
        self._status_code = status_code
 | 
						|
        self._contents = contents
 | 
						|
        self._headers = headers
 | 
						|
 | 
						|
    @property
 | 
						|
    def code(self):
 | 
						|
        return self._status_code
 | 
						|
 | 
						|
    @property
 | 
						|
    def contents(self):
 | 
						|
        return self._contents
 | 
						|
 | 
						|
    @property
 | 
						|
    def headers(self):
 | 
						|
        return self._headers
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        if not self.contents:
 | 
						|
            return ''
 | 
						|
        else:
 | 
						|
            return str(self.contents)
 | 
						|
 | 
						|
    def ok(self, redirects_ok=False):
 | 
						|
        upper = 300
 | 
						|
        if redirects_ok:
 | 
						|
            upper = 400
 | 
						|
        if self.code >= 200 and self.code < upper:
 | 
						|
            return True
 | 
						|
        else:
 | 
						|
            return False
 | 
						|
 | 
						|
 | 
						|
def readurl(url, data=None, timeout=None,
 | 
						|
            retries=0, sec_between=1, headers=None):
 | 
						|
 | 
						|
    req_args = {}
 | 
						|
    req_args['url'] = url
 | 
						|
    if data is not None:
 | 
						|
        req_args['data'] = urllib.urlencode(data)
 | 
						|
 | 
						|
    if not headers:
 | 
						|
        headers = {
 | 
						|
            'User-Agent': 'Cloud-Init/%s' % (version.version_string()),
 | 
						|
        }
 | 
						|
 | 
						|
    req_args['headers'] = headers
 | 
						|
    req = urllib2.Request(**req_args)
 | 
						|
 | 
						|
    retries = max(retries, 0)
 | 
						|
    attempts = retries + 1
 | 
						|
 | 
						|
    excepts = []
 | 
						|
    LOG.debug(("Attempting to open '%s' with %s attempts"
 | 
						|
               " (%s retries, timeout=%s) to be performed"),
 | 
						|
              url, attempts, retries, timeout)
 | 
						|
    open_args = {}
 | 
						|
    if timeout is not None:
 | 
						|
        open_args['timeout'] = int(timeout)
 | 
						|
    for i in range(0, attempts):
 | 
						|
        try:
 | 
						|
            with closing(urllib2.urlopen(req, **open_args)) as rh:
 | 
						|
                content = rh.read()
 | 
						|
                status = rh.getcode()
 | 
						|
                if status is None:
 | 
						|
                    # This seems to happen when files are read...
 | 
						|
                    status = 200
 | 
						|
                headers = {}
 | 
						|
                if rh.headers:
 | 
						|
                    headers = dict(rh.headers)
 | 
						|
                LOG.debug("Read from %s (%s, %sb) after %s attempts",
 | 
						|
                          url, status, len(content), (i + 1))
 | 
						|
                return UrlResponse(status, content, headers)
 | 
						|
        except urllib2.HTTPError as e:
 | 
						|
            excepts.append(e)
 | 
						|
        except urllib2.URLError as e:
 | 
						|
            # This can be a message string or
 | 
						|
            # another exception instance
 | 
						|
            # (socket.error for remote URLs, OSError for local URLs).
 | 
						|
            if (isinstance(e.reason, (OSError)) and
 | 
						|
                e.reason.errno == errno.ENOENT):
 | 
						|
                excepts.append(e.reason)
 | 
						|
            else:
 | 
						|
                excepts.append(e)
 | 
						|
        except Exception as e:
 | 
						|
            excepts.append(e)
 | 
						|
        if i + 1 < attempts:
 | 
						|
            LOG.debug("Please wait %s seconds while we wait to try again",
 | 
						|
                     sec_between)
 | 
						|
            time.sleep(sec_between)
 | 
						|
 | 
						|
    # Didn't work out
 | 
						|
    LOG.warn("Failed reading from %s after %s attempts", url, attempts)
 | 
						|
 | 
						|
    # It must of errored at least once for code
 | 
						|
    # to get here so re-raise the last error
 | 
						|
    LOG.debug("%s errors occured, re-raising the last one", len(excepts))
 | 
						|
    raise excepts[-1]
 | 
						|
 | 
						|
 | 
						|
def wait_for_url(urls, max_wait=None, timeout=None,
 | 
						|
                 status_cb=None, headers_cb=None, sleep_time=1):
 | 
						|
    """
 | 
						|
    urls:      a list of urls to try
 | 
						|
    max_wait:  roughly the maximum time to wait before giving up
 | 
						|
               The max time is *actually* len(urls)*timeout as each url will
 | 
						|
               be tried once and given the timeout provided.
 | 
						|
    timeout:   the timeout provided to urllib2.urlopen
 | 
						|
    status_cb: call method with string message when a url is not available
 | 
						|
    headers_cb: call method with single argument of url to get headers
 | 
						|
                for request.
 | 
						|
 | 
						|
    the idea of this routine is to wait for the EC2 metdata service to
 | 
						|
    come up.  On both Eucalyptus and EC2 we have seen the case where
 | 
						|
    the instance hit the MD before the MD service was up.  EC2 seems
 | 
						|
    to have permenantely fixed this, though.
 | 
						|
 | 
						|
    In openstack, the metadata service might be painfully slow, and
 | 
						|
    unable to avoid hitting a timeout of even up to 10 seconds or more
 | 
						|
    (LP: #894279) for a simple GET.
 | 
						|
 | 
						|
    Offset those needs with the need to not hang forever (and block boot)
 | 
						|
    on a system where cloud-init is configured to look for EC2 Metadata
 | 
						|
    service but is not going to find one.  It is possible that the instance
 | 
						|
    data host (169.254.169.254) may be firewalled off Entirely for a sytem,
 | 
						|
    meaning that the connection will block forever unless a timeout is set.
 | 
						|
    """
 | 
						|
    start_time = time.time()
 | 
						|
 | 
						|
    def log_status_cb(msg):
 | 
						|
        LOG.debug(msg)
 | 
						|
 | 
						|
    if status_cb is None:
 | 
						|
        status_cb = log_status_cb
 | 
						|
 | 
						|
    def timeup(max_wait, start_time):
 | 
						|
        return ((max_wait <= 0 or max_wait is None) or
 | 
						|
                (time.time() - start_time > max_wait))
 | 
						|
 | 
						|
    loop_n = 0
 | 
						|
    while True:
 | 
						|
        sleep_time = int(loop_n / 5) + 1
 | 
						|
        for url in urls:
 | 
						|
            now = time.time()
 | 
						|
            if loop_n != 0:
 | 
						|
                if timeup(max_wait, start_time):
 | 
						|
                    break
 | 
						|
                if timeout and (now + timeout > (start_time + max_wait)):
 | 
						|
                    # shorten timeout to not run way over max_time
 | 
						|
                    timeout = int((start_time + max_wait) - now)
 | 
						|
 | 
						|
            reason = ""
 | 
						|
            try:
 | 
						|
                if headers_cb is not None:
 | 
						|
                    headers = headers_cb(url)
 | 
						|
                else:
 | 
						|
                    headers = {}
 | 
						|
 | 
						|
                resp = readurl(url, headers=headers, timeout=timeout)
 | 
						|
                if not resp.contents:
 | 
						|
                    reason = "empty response [%s]" % (resp.code)
 | 
						|
                elif not resp.ok():
 | 
						|
                    reason = "bad status code [%s]" % (resp.code)
 | 
						|
                else:
 | 
						|
                    return url
 | 
						|
            except urllib2.HTTPError as e:
 | 
						|
                reason = "http error [%s]" % e.code
 | 
						|
            except urllib2.URLError as e:
 | 
						|
                reason = "url error [%s]" % e.reason
 | 
						|
            except socket.timeout as e:
 | 
						|
                reason = "socket timeout [%s]" % e
 | 
						|
            except Exception as e:
 | 
						|
                reason = "unexpected error [%s]" % e
 | 
						|
 | 
						|
            time_taken = int(time.time() - start_time)
 | 
						|
            status_msg = "Calling '%s' failed [%s/%ss]: %s" % (url,
 | 
						|
                                                             time_taken,
 | 
						|
                                                             max_wait, reason)
 | 
						|
            status_cb(status_msg)
 | 
						|
 | 
						|
        if timeup(max_wait, start_time):
 | 
						|
            break
 | 
						|
 | 
						|
        loop_n = loop_n + 1
 | 
						|
        LOG.debug("Please wait %s seconds while we wait to try again",
 | 
						|
                  sleep_time)
 | 
						|
        time.sleep(sleep_time)
 | 
						|
 | 
						|
    return False
 |