~maas-committers/maas/trunk

« back to all changes in this revision

Viewing changes to src/metadataserver/commissioning/snippets/maas_api_helper.py

[r=allenap][bug=][author=jtv] Make commissioning nodes download their commissioning scripts (such as lshw) from the metadata service.  And componentize the big commissioning script.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from email.utils import parsedate
 
2
import sys
 
3
import time
 
4
import urllib2
 
5
 
 
6
import oauth.oauth as oauth
 
7
import yaml
 
8
 
 
9
 
 
10
__all__ = [
 
11
    'geturl',
 
12
    'read_config',
 
13
    ]
 
14
 
 
15
 
 
16
def read_config(url, creds):
 
17
    """Read cloud-init config from given `url` into `creds` dict.
 
18
 
 
19
    Updates any keys in `creds` that are None with their corresponding
 
20
    values in the config.
 
21
 
 
22
    Important keys include `metadata_url`, and the actual OAuth
 
23
    credentials.
 
24
    """
 
25
    if url.startswith("http://") or url.startswith("https://"):
 
26
        cfg_str = urllib2.urlopen(urllib2.Request(url=url))
 
27
    else:
 
28
        if url.startswith("file://"):
 
29
            url = url[7:]
 
30
        cfg_str = open(url, "r").read()
 
31
 
 
32
    cfg = yaml.safe_load(cfg_str)
 
33
 
 
34
    # Support reading cloud-init config for MAAS datasource.
 
35
    if 'datasource' in cfg:
 
36
        cfg = cfg['datasource']['MAAS']
 
37
 
 
38
    for key in creds.keys():
 
39
        if key in cfg and creds[key] == None:
 
40
            creds[key] = cfg[key]
 
41
 
 
42
 
 
43
def oauth_headers(url, consumer_key, token_key, token_secret, consumer_secret,
 
44
                  clockskew=0):
 
45
    """Build OAuth headers using given credentials."""
 
46
    consumer = oauth.OAuthConsumer(consumer_key, consumer_secret)
 
47
    token = oauth.OAuthToken(token_key, token_secret)
 
48
 
 
49
    timestamp = int(time.time()) + clockskew
 
50
 
 
51
    params = {
 
52
        'oauth_version': "1.0",
 
53
        'oauth_nonce': oauth.generate_nonce(),
 
54
        'oauth_timestamp': timestamp,
 
55
        'oauth_token': token.key,
 
56
        'oauth_consumer_key': consumer.key,
 
57
    }
 
58
    req = oauth.OAuthRequest(http_url=url, parameters=params)
 
59
    req.sign_request(
 
60
        oauth.OAuthSignatureMethod_PLAINTEXT(), consumer, token)
 
61
    return(req.to_header())
 
62
 
 
63
 
 
64
def authenticate_headers(url, headers, creds, clockskew):
 
65
    """Update and sign a dict of request headers."""
 
66
    if creds.get('consumer_key', None) != None:
 
67
        headers.update(oauth_headers(
 
68
            url,
 
69
            consumer_key=creds['consumer_key'],
 
70
            token_key=creds['token_key'],
 
71
            token_secret=creds['token_secret'],
 
72
            consumer_secret=creds['consumer_secret'],
 
73
            clockskew=clockskew))
 
74
 
 
75
 
 
76
def warn(msg):
 
77
    sys.stderr.write(msg + "\n")
 
78
 
 
79
 
 
80
def geturl(url, creds, headers=None, data=None):
 
81
    # Takes a dict of creds to be passed through to oauth_headers,
 
82
    #   so it should have consumer_key, token_key, ...
 
83
    if headers is None:
 
84
        headers = {}
 
85
    else:
 
86
        headers = dict(headers)
 
87
 
 
88
    clockskew = 0
 
89
 
 
90
    exc = Exception("Unexpected Error")
 
91
    for naptime in (1, 1, 2, 4, 8, 16, 32):
 
92
        authenticate_headers(url, headers, creds, clockskew)
 
93
        try:
 
94
            req = urllib2.Request(url=url, data=data, headers=headers)
 
95
            return urllib2.urlopen(req).read()
 
96
        except urllib2.HTTPError as exc:
 
97
            if 'date' not in exc.headers:
 
98
                warn("date field not in %d headers" % exc.code)
 
99
                pass
 
100
            elif exc.code in (401, 403):
 
101
                date = exc.headers['date']
 
102
                try:
 
103
                    ret_time = time.mktime(parsedate(date))
 
104
                    clockskew = int(ret_time - time.time())
 
105
                    warn("updated clock skew to %d" % clockskew)
 
106
                except:
 
107
                    warn("failed to convert date '%s'" % date)
 
108
        except Exception as exc:
 
109
            pass
 
110
 
 
111
        warn("request to %s failed. sleeping %d.: %s" % (url, naptime, exc))
 
112
        time.sleep(naptime)
 
113
 
 
114
    raise exc