~hazmat/pyjuju/proposed-support

« back to all changes in this revision

Viewing changes to juju/providers/maas/maas.py

  • Committer: kapil.thangavelu at canonical
  • Date: 2012-05-22 22:08:15 UTC
  • mfrom: (484.1.53 trunk)
  • Revision ID: kapil.thangavelu@canonical.com-20120522220815-acyt8m89i9ybe0w1
merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright 2012 Canonical Ltd.  This software is licensed under the
2
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
3
 
4
 
"""MaaS API client for Juju"""
 
4
"""MAAS API client for Juju"""
5
5
 
6
6
from base64 import b64encode
7
7
import json
 
8
import re
8
9
from urllib import urlencode
 
10
from urlparse import urljoin
9
11
 
 
12
from juju.errors import ProviderError
10
13
from juju.providers.common.utils import convert_unknown_error
11
 
from juju.providers.maas.auth import MaaSOAuthConnection
 
14
from juju.providers.maas.auth import MAASOAuthConnection
12
15
from juju.providers.maas.files import encode_multipart_data
13
16
 
14
17
 
15
18
CONSUMER_SECRET = ""
16
19
 
17
20
 
18
 
class MaaSClient(MaaSOAuthConnection):
 
21
_re_resource_uri = re.compile(
 
22
    '/api/(?P<version>[^/]+)/nodes/(?P<system_id>[^/]+)/?')
 
23
 
 
24
 
 
25
def extract_system_id(resource_uri):
 
26
    """Extract a system ID from a resource URI.
 
27
 
 
28
    This is fairly unforgiving; an exception is raised if the URI given does
 
29
    not look like a MAAS node resource URI.
 
30
 
 
31
    :param resource_uri: A URI that corresponds to a MAAS node resource.
 
32
    :raises: :exc:`juju.errors.ProviderError` when `resource_uri` does not
 
33
        resemble a MAAS node resource URI.
 
34
    """
 
35
    match = _re_resource_uri.search(resource_uri)
 
36
    if match is None:
 
37
        raise ProviderError(
 
38
            "%r does not resemble a MAAS resource URI." % (resource_uri,))
 
39
    else:
 
40
        return match.group("system_id")
 
41
 
 
42
 
 
43
class MAASClient(MAASOAuthConnection):
19
44
 
20
45
    def __init__(self, config):
21
 
        """Initialise an API client for MaaS.
 
46
        """Initialise an API client for MAAS.
22
47
 
23
48
        :param config: a dict of configuration values; must contain
24
49
            'maas-server', 'maas-oauth', 'admin-secret'
25
50
        """
26
51
        self.url = config["maas-server"]
27
 
        if self.url.endswith('/'):
28
 
            # Remove the trailing slash as MaaS provides resource uris
29
 
            # with it included at the start and then goes bang if you
30
 
            # give it two slashes.
31
 
            self.url = self.url[:-1]
 
52
        if not self.url.endswith('/'):
 
53
            self.url += "/"
32
54
        self.oauth_info = config["maas-oauth"]
33
55
        self.admin_secret = config["admin-secret"]
34
 
        super(MaaSClient, self).__init__(self.oauth_info)
35
 
 
36
 
    def _get(self, path, params):
37
 
        """Dispatch a C{GET} call to a MaaS server.
38
 
 
39
 
        :param uri: The MaaS path for the endpoint to call.
 
56
        super(MAASClient, self).__init__(self.oauth_info)
 
57
 
 
58
    def get(self, path, params):
 
59
        """Dispatch a C{GET} call to a MAAS server.
 
60
 
 
61
        :param uri: The MAAS path for the endpoint to call.
40
62
        :param params: A C{dict} of parameters - or sequence of 2-tuples - to
41
63
            encode into the request.
42
64
        :return: A Deferred which fires with the result of the call.
43
65
        """
44
 
        url = "%s%s?%s" % (self.url, path, urlencode(params))
 
66
        url = "%s?%s" % (urljoin(self.url, path), urlencode(params))
45
67
        d = self.dispatch_query(url)
46
68
        d.addCallback(json.loads)
47
69
        d.addErrback(convert_unknown_error)
48
70
        return d
49
71
 
50
 
    def _post(self, path, params):
51
 
        """Dispatch a C{POST} call to a MaaS server.
 
72
    def post(self, path, params):
 
73
        """Dispatch a C{POST} call to a MAAS server.
52
74
 
53
 
        :param uri: The MaaS path for the endpoint to call.
 
75
        :param uri: The MAAS path for the endpoint to call.
54
76
        :param params: A C{dict} of parameters to encode into the request.
55
77
        :return: A Deferred which fires with the result of the call.
56
78
        """
57
 
        url = self.url + path
 
79
        url = urljoin(self.url, path)
58
80
        body, headers = encode_multipart_data(params, {})
59
81
        d = self.dispatch_query(url, "POST", headers=headers, data=body)
60
82
        d.addCallback(json.loads)
61
83
        d.addErrback(convert_unknown_error)
62
84
        return d
63
85
 
64
 
    def get_nodes(self, system_ids=None):
65
 
        """Ask MaaS to return a list of all the nodes it knows about.
 
86
    def get_nodes(self, resource_uris=None):
 
87
        """Ask MAAS to return a list of all the nodes it knows about.
66
88
 
 
89
        :param resource_uris: The MAAS URIs for the nodes you want to get.
67
90
        :return: A Deferred whose value is the list of nodes.
68
91
        """
69
 
        params = [("op", "list")]
70
 
        if system_ids is not None:
71
 
            params.extend(("id", system_id) for system_id in system_ids)
72
 
        return self._get("/api/1.0/nodes/", params)
 
92
        params = [("op", "list_allocated")]
 
93
        if resource_uris is not None:
 
94
            params.extend(
 
95
                ("id", extract_system_id(resource_uri))
 
96
                for resource_uri in resource_uris)
 
97
        return self.get("api/1.0/nodes/", params)
73
98
 
74
 
    def acquire_node(self):
75
 
        """Ask MaaS to assign a node to us.
 
99
    def acquire_node(self, constraints=None):
 
100
        """Ask MAAS to assign a node to us.
76
101
 
77
102
        :return: A Deferred whose value is the resource URI to the node
78
103
            that was acquired.
79
104
        """
80
105
        params = {"op": "acquire"}
81
 
        return self._post("/api/1.0/nodes/", params)
 
106
        if constraints is not None:
 
107
            name = constraints["maas-name"]
 
108
            if name is not None:
 
109
                params["name"] = name
 
110
        return self.post("api/1.0/nodes/", params)
82
111
 
83
112
    def start_node(self, resource_uri, user_data):
84
 
        """Ask MaaS to start a node.
 
113
        """Ask MAAS to start a node.
85
114
 
86
 
        :param resource_uri: The MaaS URI for the node you want to start.
87
 
        :param user_data: Any blob of data to be passed to MaaS. Must be
 
115
        :param resource_uri: The MAAS URI for the node you want to start.
 
116
        :param user_data: Any blob of data to be passed to MAAS. Must be
88
117
            possible to encode as base64.
89
118
        :return: A Deferred whose value is the resource data for the node
90
119
            as returned by get_nodes().
92
121
        assert isinstance(user_data, str), (
93
122
            "User data must be a byte string.")
94
123
        params = {"op": "start", "user_data": b64encode(user_data)}
95
 
        return self._post(resource_uri, params)
 
124
        return self.post(resource_uri, params)
96
125
 
97
126
    def stop_node(self, resource_uri):
98
127
        """Ask maas to shut down a node.
99
128
 
100
 
        :param resource_uri: The MaaS URI for the node you want to  stop.
 
129
        :param resource_uri: The MAAS URI for the node you want to stop.
101
130
        :return: A Deferred whose value is the resource data for the node
102
131
            as returned by get_nodes().
103
132
        """
104
133
        params = {"op": "stop"}
105
 
        return self._post(resource_uri, params)
 
134
        return self.post(resource_uri, params)
106
135
 
107
136
    def release_node(self, resource_uri):
108
 
        """Ask MaaS to release a node from our ownership.
 
137
        """Ask MAAS to release a node from our ownership.
109
138
 
110
 
        :param resource_uri: The URI in MaaS for the node you want to release.
 
139
        :param resource_uri: The URI in MAAS for the node you want to release.
111
140
        :return: A Deferred which fires with the resource data for the node
112
141
            just released.
113
142
        """
114
143
        params = {"op": "release"}
115
 
        return self._post(resource_uri, params)
 
144
        return self.post(resource_uri, params)