1
Author: Scott Moser <smoser@ubuntu.com>
2
Bug: https://launchpad.net/bugs/978127
3
Applied-Upstream: revno 666 and 678
4
Description: DataSourceMAAS: adjust oauth request timestamps on 401 or 403
5
In the event of a 401 or 403 (Unauthorized) in oauth, try set a
6
'oauth_clockskew' variable. In future headers, use a time created by
7
'time.time() + self.oauth_clockskew'. The idea here is that if the local time
8
is bad (or even if the server time is bad) we will essentially use something
9
that should be similar to the remote clock.
10
--- a/cloudinit/DataSourceMAAS.py
11
+++ b/cloudinit/DataSourceMAAS.py
12
@@ -20,6 +20,7 @@ import cloudinit.DataSource as DataSourc
14
from cloudinit import seeddir as base_seeddir
15
from cloudinit import log
16
+from email.utils import parsedate
17
import cloudinit.util as util
19
import oauth.oauth as oauth
20
@@ -29,6 +30,7 @@ import time
23
MD_VERSION = "2012-03-01"
27
class DataSourceMAAS(DataSource.DataSource):
28
@@ -42,6 +44,7 @@ class DataSourceMAAS(DataSource.DataSour
30
seeddir = base_seeddir + '/maas'
32
+ oauth_clockskew = None
35
return("DataSourceMAAS[%s]" % self.baseurl)
36
@@ -92,9 +95,14 @@ class DataSourceMAAS(DataSource.DataSour
38
consumer_secret = mcfg.get('consumer_secret', "")
41
+ if self.oauth_clockskew:
42
+ timestamp = int(time.time()) + self.oauth_clockskew
44
return(oauth_headers(url=url, consumer_key=mcfg['consumer_key'],
45
token_key=mcfg['token_key'], token_secret=mcfg['token_secret'],
46
- consumer_secret=consumer_secret))
47
+ consumer_secret=consumer_secret,
48
+ timestamp=timestamp))
50
def wait_for_metadata_service(self, url):
52
@@ -119,7 +127,8 @@ class DataSourceMAAS(DataSource.DataSour
53
starttime = time.time()
54
check_url = "%s/%s/meta-data/instance-id" % (url, MD_VERSION)
55
url = util.wait_for_url(urls=[check_url], max_wait=max_wait,
56
- timeout=timeout, status_cb=log.warn,
57
+ timeout=timeout, status_cb=LOG.warn,
58
+ exception_cb=self._except_cb,
59
headers_cb=self.md_headers)
62
@@ -130,6 +139,26 @@ class DataSourceMAAS(DataSource.DataSour
66
+ def _except_cb(self, msg, exception):
67
+ if not (isinstance(exception, urllib2.HTTPError) and
68
+ (exception.code == 403 or exception.code == 401)):
70
+ if 'date' not in exception.headers:
71
+ LOG.warn("date field not in %d headers" % exception.code)
74
+ date = exception.headers['date']
77
+ ret_time = time.mktime(parsedate(date))
79
+ LOG.warn("failed to convert datetime '%s'")
82
+ self.oauth_clockskew = int(ret_time - time.time())
83
+ LOG.warn("set oauth clockskew to %d" % self.oauth_clockskew)
87
def read_maas_seed_dir(seed_d):
89
@@ -220,13 +249,20 @@ def check_seed_contents(content, seed):
93
-def oauth_headers(url, consumer_key, token_key, token_secret, consumer_secret):
94
+def oauth_headers(url, consumer_key, token_key, token_secret, consumer_secret,
96
consumer = oauth.OAuthConsumer(consumer_key, consumer_secret)
97
token = oauth.OAuthToken(token_key, token_secret)
99
+ if timestamp is None:
100
+ ts = int(time.time())
105
'oauth_version': "1.0",
106
'oauth_nonce': oauth.generate_nonce(),
107
- 'oauth_timestamp': int(time.time()),
108
+ 'oauth_timestamp': ts,
109
'oauth_token': token.key,
110
'oauth_consumer_key': consumer.key,
112
--- a/cloudinit/util.py
113
+++ b/cloudinit/util.py
114
@@ -756,7 +756,7 @@ def mount_callback_umount(device, callba
117
def wait_for_url(urls, max_wait=None, timeout=None,
118
- status_cb=None, headers_cb=None):
119
+ status_cb=None, headers_cb=None, exception_cb=None):
121
urls: a list of urls to try
122
max_wait: roughly the maximum time to wait before giving up
123
@@ -766,6 +766,8 @@ def wait_for_url(urls, max_wait=None, ti
124
status_cb: call method with string message when a url is not available
125
headers_cb: call method with single argument of url to get headers
127
+ exception_cb: call method with 2 arguments 'msg' (per status_cb) and
128
+ 'exception', the exception that occurred.
130
the idea of this routine is to wait for the EC2 metdata service to
131
come up. On both Eucalyptus and EC2 we have seen the case where
132
@@ -817,9 +819,15 @@ def wait_for_url(urls, max_wait=None, ti
134
req = urllib2.Request(url, data=None, headers=headers)
135
resp = urllib2.urlopen(req, timeout=timeout)
136
- if resp.read() != "":
137
+ contents = resp.read()
139
+ reason = "empty data [%s]" % (resp.code)
140
+ e = ValueError(reason)
141
+ elif not (resp.code >= 200 and resp.code < 400):
142
+ reason = "bad status code [%s]" % (resp.code)
143
+ e = ValueError(reason)
146
- reason = "empty data [%s]" % resp.getcode()
147
except urllib2.HTTPError as e:
148
reason = "http error [%s]" % e.code
149
except urllib2.URLError as e:
150
@@ -829,9 +837,12 @@ def wait_for_url(urls, max_wait=None, ti
151
except Exception as e:
152
reason = "unexpected error [%s]" % e
154
- status_cb("'%s' failed [%s/%ss]: %s" %
155
- (url, int(time.time() - starttime), max_wait,
157
+ status_msg = ("'%s' failed [%s/%ss]: %s" %
158
+ (url, int(time.time() - starttime), max_wait,
160
+ status_cb(status_msg)
162
+ exception_cb(msg=status_msg, exception=e)
164
if timeup(max_wait, starttime):