summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog2
-rwxr-xr-xbin/cloud-init3
-rw-r--r--cloudinit/distros/__init__.py2
-rw-r--r--cloudinit/patcher.py58
-rw-r--r--cloudinit/sources/DataSourceMAAS.py43
-rw-r--r--cloudinit/sources/__init__.py2
-rw-r--r--cloudinit/stages.py2
-rw-r--r--cloudinit/url_helper.py11
-rwxr-xr-xtools/write-ssh-key-fingerprints3
9 files changed, 116 insertions, 10 deletions
diff --git a/ChangeLog b/ChangeLog
index aca34d6e..c5dcd418 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,6 @@
0.7.0:
+ - catch signals and exit rather than stack tracing
+ - if logging fails, enable a fallback logger by patching the logging module
- do not 'start networking' in cloud-init-nonet, but add
cloud-init-container job that runs only if in container and emits
net-device-added (LP: #1031065)
diff --git a/bin/cloud-init b/bin/cloud-init
index a6ad14a6..c5a5b949 100755
--- a/bin/cloud-init
+++ b/bin/cloud-init
@@ -33,6 +33,9 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(
if os.path.exists(os.path.join(possible_topdir, "cloudinit", "__init__.py")):
sys.path.insert(0, possible_topdir)
+from cloudinit import patcher
+patcher.patch()
+
from cloudinit import log as logging
from cloudinit import netinfo
from cloudinit import signal_handler
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index 442e75b4..549c1612 100644
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -340,7 +340,7 @@ class Distro(object):
content += "\n"
if not os.path.exists(sudo_file):
- util.write_file(sudo_file, content, 0644)
+ util.write_file(sudo_file, content, 0440)
else:
try:
diff --git a/cloudinit/patcher.py b/cloudinit/patcher.py
new file mode 100644
index 00000000..0f3c034e
--- /dev/null
+++ b/cloudinit/patcher.py
@@ -0,0 +1,58 @@
+# vi: ts=4 expandtab
+#
+# Copyright (C) 2012 Canonical Ltd.
+# Copyright (C) 2012 Yahoo! Inc.
+#
+# Author: Scott Moser <scott.moser@canonical.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/>.
+
+import imp
+import logging
+import sys
+
+# Default fallback format
+FALL_FORMAT = ('FALLBACK: %(asctime)s - %(filename)s[%(levelname)s]: ' +
+ '%(message)s')
+
+
+class QuietStreamHandler(logging.StreamHandler):
+ def handleError(self, record):
+ pass
+
+
+def _patch_logging():
+ # Replace 'handleError' with one that will be more
+ # tolerant of errors in that it can avoid
+ # re-notifying on exceptions and when errors
+ # do occur, it can at least try to write to
+ # sys.stderr using a fallback logger
+ fallback_handler = QuietStreamHandler(sys.stderr)
+ fallback_handler.setFormatter(logging.Formatter(FALL_FORMAT))
+
+ def handleError(self, record): # pylint: disable=W0613
+ try:
+ fallback_handler.handle(record)
+ fallback_handler.flush()
+ except IOError:
+ pass
+ setattr(logging.Handler, 'handleError', handleError)
+
+
+def patch():
+ imp.acquire_lock()
+ try:
+ _patch_logging()
+ finally:
+ imp.release_lock()
diff --git a/cloudinit/sources/DataSourceMAAS.py b/cloudinit/sources/DataSourceMAAS.py
index c568d365..581e9a4b 100644
--- a/cloudinit/sources/DataSourceMAAS.py
+++ b/cloudinit/sources/DataSourceMAAS.py
@@ -18,6 +18,7 @@
# 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 email.utils import parsedate
import errno
import oauth.oauth as oauth
import os
@@ -46,6 +47,7 @@ class DataSourceMAAS(sources.DataSource):
sources.DataSource.__init__(self, sys_cfg, distro, paths)
self.base_url = None
self.seed_dir = os.path.join(paths.seed_dir, 'maas')
+ self.oauth_clockskew = None
def __str__(self):
return "%s [%s]" % (util.obj_name(self), self.base_url)
@@ -95,11 +97,17 @@ class DataSourceMAAS(sources.DataSource):
return {}
consumer_secret = mcfg.get('consumer_secret', "")
+
+ timestamp = None
+ if self.oauth_clockskew:
+ timestamp = int(time.time()) + self.oauth_clockskew
+
return oauth_headers(url=url,
consumer_key=mcfg['consumer_key'],
token_key=mcfg['token_key'],
token_secret=mcfg['token_secret'],
- consumer_secret=consumer_secret)
+ consumer_secret=consumer_secret,
+ timestamp=timestamp)
def wait_for_metadata_service(self, url):
mcfg = self.ds_cfg
@@ -124,7 +132,7 @@ class DataSourceMAAS(sources.DataSource):
check_url = "%s/%s/meta-data/instance-id" % (url, MD_VERSION)
urls = [check_url]
url = uhelp.wait_for_url(urls=urls, max_wait=max_wait,
- timeout=timeout, status_cb=LOG.warn,
+ timeout=timeout, exception_cb=self._except_cb,
headers_cb=self.md_headers)
if url:
@@ -135,6 +143,26 @@ class DataSourceMAAS(sources.DataSource):
return bool(url)
+ def _except_cb(self, msg, exception):
+ if not (isinstance(exception, urllib2.HTTPError) and
+ exception.code == 403):
+ return
+ if 'date' not in exception.headers:
+ LOG.warn("date field not in 403 headers")
+ return
+
+ date = exception.headers['date']
+
+ try:
+ ret_time = time.mktime(parsedate(date))
+ except:
+ LOG.warn("failed to convert datetime '%s'")
+ return
+
+ self.oauth_clockskew = int(ret_time - time.time())
+ LOG.warn("set oauth clockskew to %d" % self.oauth_clockskew)
+ return
+
def read_maas_seed_dir(seed_d):
"""
@@ -229,13 +257,20 @@ def check_seed_contents(content, seed):
return (userdata, md)
-def oauth_headers(url, consumer_key, token_key, token_secret, consumer_secret):
+def oauth_headers(url, consumer_key, token_key, token_secret, consumer_secret,
+ timestamp=None):
consumer = oauth.OAuthConsumer(consumer_key, consumer_secret)
token = oauth.OAuthToken(token_key, token_secret)
+
+ if timestamp is None:
+ ts = int(time.time())
+ else:
+ ts = timestamp
+
params = {
'oauth_version': "1.0",
'oauth_nonce': oauth.generate_nonce(),
- 'oauth_timestamp': int(time.time()),
+ 'oauth_timestamp': ts,
'oauth_token': token.key,
'oauth_consumer_key': consumer.key,
}
diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py
index 6f126091..04083d0c 100644
--- a/cloudinit/sources/__init__.py
+++ b/cloudinit/sources/__init__.py
@@ -173,7 +173,7 @@ class DataSource(object):
# make up a hostname (LP: #475354) in format ip-xx.xx.xx.xx
lhost = self.metadata['local-hostname']
if util.is_ipv4(lhost):
- toks = [ "ip-%s" % lhost.replace(".", "-") ]
+ toks = ["ip-%s" % lhost.replace(".", "-")]
else:
toks = lhost.split(".")
diff --git a/cloudinit/stages.py b/cloudinit/stages.py
index af902925..4ed1a750 100644
--- a/cloudinit/stages.py
+++ b/cloudinit/stages.py
@@ -240,7 +240,7 @@ class Init(object):
return ds
def _get_instance_subdirs(self):
- return ['handlers', 'scripts', 'sems']
+ return ['handlers', 'scripts', 'sem']
def _get_ipath(self, subname=None):
# Force a check to see if anything
diff --git a/cloudinit/url_helper.py b/cloudinit/url_helper.py
index 732d6aec..f3e3fd7e 100644
--- a/cloudinit/url_helper.py
+++ b/cloudinit/url_helper.py
@@ -136,7 +136,8 @@ def readurl(url, data=None, timeout=None,
def wait_for_url(urls, max_wait=None, timeout=None,
- status_cb=None, headers_cb=None, sleep_time=1):
+ status_cb=None, headers_cb=None, sleep_time=1,
+ exception_cb=None):
"""
urls: a list of urls to try
max_wait: roughly the maximum time to wait before giving up
@@ -146,6 +147,8 @@ def wait_for_url(urls, max_wait=None, timeout=None,
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.
+ exception_cb: call method with 2 arguments 'msg' (per status_cb) and
+ 'exception', the exception that occurred.
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
@@ -164,7 +167,7 @@ def wait_for_url(urls, max_wait=None, timeout=None,
"""
start_time = time.time()
- def log_status_cb(msg):
+ def log_status_cb(msg, exc=None):
LOG.debug(msg)
if status_cb is None:
@@ -196,8 +199,10 @@ def wait_for_url(urls, max_wait=None, timeout=None,
resp = readurl(url, headers=headers, timeout=timeout)
if not resp.contents:
reason = "empty response [%s]" % (resp.code)
+ e = ValueError(reason)
elif not resp.ok():
reason = "bad status code [%s]" % (resp.code)
+ e = ValueError(reason)
else:
return url
except urllib2.HTTPError as e:
@@ -214,6 +219,8 @@ def wait_for_url(urls, max_wait=None, timeout=None,
time_taken,
max_wait, reason)
status_cb(status_msg)
+ if exception_cb:
+ exception_cb(msg=status_msg, exception=e)
if timeup(max_wait, start_time):
break
diff --git a/tools/write-ssh-key-fingerprints b/tools/write-ssh-key-fingerprints
index 5723c989..aa1f3c38 100755
--- a/tools/write-ssh-key-fingerprints
+++ b/tools/write-ssh-key-fingerprints
@@ -1,4 +1,5 @@
#!/bin/sh
+exec 2>&1
fp_blist=",${1},"
key_blist=",${2},"
{
@@ -15,7 +16,7 @@ done
echo "-----END SSH HOST KEY FINGERPRINTS-----"
echo "#############################################################"
-} | logger -p user.info -s -t "ec2"
+} | logger -p user.info --stderr -t "ec2"
echo -----BEGIN SSH HOST KEY KEYS-----
for f in /etc/ssh/ssh_host_*key.pub; do