Merge lp:~smoser/maas/trunk.maas-signal-clockskew into lp:~maas-committers/maas/trunk

Proposed by Scott Moser
Status: Merged
Approved by: Scott Moser
Approved revision: no longer in the source branch.
Merged at revision: 1242
Proposed branch: lp:~smoser/maas/trunk.maas-signal-clockskew
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 82 lines (+44/-9)
1 file modified
etc/maas/commissioning-user-data (+44/-9)
To merge this branch: bzr merge lp:~smoser/maas/trunk.maas-signal-clockskew
Reviewer Review Type Date Requested Status
Robie Basak (community) Approve
Review via email: mp+128794@code.launchpad.net

Commit message

make maas-signal adjust clock and retry on 401 or 403 errors.

oauth failures due to incorrect clock by maas return error 401.
If we see this error, then maas-signal will read the timestamp in the
response header and update future requests based on that time.

This is the same approach fix that went into cloud-init.

Description of the change

make maas-signal adjust clock and retry on 401 or 403 errors.

oauth failures due to incorrect clock by maas return error 401.
If we see this error, then maas-signal will read the timestamp in the
response header and update future requests based on that time.

This is the same approach fix that went into cloud-init.

To post a comment you must log in.
Revision history for this message
Robie Basak (racb) wrote :

Looks good! I've tested this and it appears to work. Commissioning does not set the RTC as expected, but the install step does.

A couple of comments, although I understand the criticality of this fix and the imminent deadline so we don't necessarily need to consider this now. Go ahead and land it.

1. Instead of hardcoding 401 and 403, we should use httplib constants.

2. I'm not sure about the use of exc to carry through the exception. In the catch clause, does that exc stay in scope for after it is set, or is there a new scope that hides the outer exc? I'm not sure. For readability, it might be worth using a different name for the inner scope and explicitly setting the one defined in the outer scope. I'm not sure about the whole mechanism, actually, but I won't think about it any longer. It'll work for now.

review: Approve
Revision history for this message
Scott Moser (smoser) wrote :

Reguarding '2', the exc works as expected:
#!/usr/bin/python
import sys
def main():
    print "HI WORLD"
    exc = Exception("unexpected error")
    for i in (1,2,3,4):
       try:
          raise(TypeError("foobar: %d" % i))
       except TypeError as exc:
          sys.stderr.write("caught exc: %s\n" % exc)
       except Exception as exc:
          sys.stderr.write("caught EXC: %s\n" % exc)
    raise exc
main()

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'etc/maas/commissioning-user-data'
2--- etc/maas/commissioning-user-data 2012-10-08 01:51:07 +0000
3+++ etc/maas/commissioning-user-data 2012-10-09 19:01:25 +0000
4@@ -305,6 +305,7 @@
5 add_bin "maas-signal" <<"END_MAAS_SIGNAL"
6 #!/usr/bin/python
7
8+from email.utils import parsedate
9 import mimetypes
10 import oauth.oauth as oauth
11 import os.path
12@@ -369,13 +370,17 @@
13 return body, headers
14
15
16-def oauth_headers(url, consumer_key, token_key, token_secret, consumer_secret):
17+def oauth_headers(url, consumer_key, token_key, token_secret, consumer_secret,
18+ clockskew=0):
19 consumer = oauth.OAuthConsumer(consumer_key, consumer_secret)
20 token = oauth.OAuthToken(token_key, token_secret)
21+
22+ timestamp = int(time.time()) + clockskew
23+
24 params = {
25 'oauth_version': "1.0",
26 'oauth_nonce': oauth.generate_nonce(),
27- 'oauth_timestamp': int(time.time()),
28+ 'oauth_timestamp': timestamp,
29 'oauth_token': token.key,
30 'oauth_consumer_key': consumer.key,
31 }
32@@ -393,13 +398,43 @@
33 else:
34 headers = dict(headers)
35
36- if creds.get('consumer_key', None) != None:
37- headers.update(oauth_headers(url,
38- consumer_key=creds['consumer_key'], token_key=creds['token_key'],
39- token_secret=creds['token_secret'],
40- consumer_secret=creds['consumer_secret']))
41- req = urllib2.Request(url=url, data=data, headers=headers)
42- return(urllib2.urlopen(req).read())
43+ clockskew = 0
44+
45+ def warn(msg):
46+ sys.stderr.write(msg + "\n")
47+
48+ exc = Exception("Unexpected Error")
49+ for naptime in (1, 1, 2, 4, 8, 16, 32):
50+ if creds.get('consumer_key', None) != None:
51+ headers.update(oauth_headers(url,
52+ consumer_key=creds['consumer_key'],
53+ token_key=creds['token_key'],
54+ token_secret=creds['token_secret'],
55+ consumer_secret=creds['consumer_secret'],
56+ clockskew=clockskew))
57+ try:
58+ req = urllib2.Request(url=url, data=data, headers=headers)
59+ return(urllib2.urlopen(req).read())
60+ except urllib2.HTTPError as exc:
61+ if 'date' not in exc.headers:
62+ warn("date field not in %d headers" % exc.code)
63+ pass
64+ elif (exc.code == 401 or exc.code == 403):
65+ date = exc.headers['date']
66+ try:
67+ ret_time = time.mktime(parsedate(date))
68+ clockskew = int(ret_time - time.time())
69+ warn("updated clock skew to %d" % clockskew)
70+ except:
71+ warn("failed to convert date '%s'" % date)
72+ except Exception as exc:
73+ pass
74+
75+ warn("request to %s failed. sleeping %d.: %s" % (url, naptime, exc))
76+ time.sleep(naptime)
77+
78+ raise exc
79+
80
81 def read_config(url, creds):
82 if url.startswith("http://") or url.startswith("https://"):