Statistics
| Branch: | Tag: | Revision:

root / astakosclient / astakosclient / __init__.py @ 1f4a46dd

History | View | Annotate | Download (40.4 kB)

1 99165736 Christos Stavrakakis
# Copyright (C) 2012, 2013 GRNET S.A. All rights reserved.
2 99165736 Christos Stavrakakis
#
3 99165736 Christos Stavrakakis
# Redistribution and use in source and binary forms, with or
4 99165736 Christos Stavrakakis
# without modification, are permitted provided that the following
5 99165736 Christos Stavrakakis
# conditions are met:
6 99165736 Christos Stavrakakis
#
7 99165736 Christos Stavrakakis
#   1. Redistributions of source code must retain the above
8 99165736 Christos Stavrakakis
#      copyright notice, this list of conditions and the following
9 99165736 Christos Stavrakakis
#      disclaimer.
10 99165736 Christos Stavrakakis
#
11 99165736 Christos Stavrakakis
#   2. Redistributions in binary form must reproduce the above
12 99165736 Christos Stavrakakis
#      copyright notice, this list of conditions and the following
13 99165736 Christos Stavrakakis
#      disclaimer in the documentation and/or other materials
14 99165736 Christos Stavrakakis
#      provided with the distribution.
15 99165736 Christos Stavrakakis
#
16 99165736 Christos Stavrakakis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 99165736 Christos Stavrakakis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 99165736 Christos Stavrakakis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 99165736 Christos Stavrakakis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 99165736 Christos Stavrakakis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 99165736 Christos Stavrakakis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 99165736 Christos Stavrakakis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 99165736 Christos Stavrakakis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 99165736 Christos Stavrakakis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 99165736 Christos Stavrakakis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 99165736 Christos Stavrakakis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 99165736 Christos Stavrakakis
# POSSIBILITY OF SUCH DAMAGE.
28 99165736 Christos Stavrakakis
#
29 99165736 Christos Stavrakakis
# The views and conclusions contained in the software and
30 99165736 Christos Stavrakakis
# documentation are those of the authors and should not be
31 99165736 Christos Stavrakakis
# interpreted as representing official policies, either expressed
32 99165736 Christos Stavrakakis
# or implied, of GRNET S.A.
33 cbc0b438 Ilias Tsitsimpis
34 2c9c147e Ilias Tsitsimpis
"""
35 2c9c147e Ilias Tsitsimpis
Simple and minimal client for the Astakos authentication service
36 2c9c147e Ilias Tsitsimpis
"""
37 2c9c147e Ilias Tsitsimpis
38 cbc0b438 Ilias Tsitsimpis
import logging
39 cbc0b438 Ilias Tsitsimpis
import urlparse
40 bc5032a4 Ilias Tsitsimpis
import urllib
41 6f64b6d0 Ilias Tsitsimpis
import hashlib
42 d2104099 Sofia Papagiannaki
from base64 import b64encode
43 8f2d7ede Ilias Tsitsimpis
from copy import copy
44 cbc0b438 Ilias Tsitsimpis
45 cbc0b438 Ilias Tsitsimpis
import simplejson
46 10797183 Ilias Tsitsimpis
from astakosclient.utils import \
47 2c9c147e Ilias Tsitsimpis
    retry_dec, scheme_to_class, parse_request, check_input, join_urls
48 f93cc364 Ilias Tsitsimpis
from astakosclient.errors import \
49 2377e7c2 Ilias Tsitsimpis
    AstakosClientException, Unauthorized, BadRequest, NotFound, Forbidden, \
50 2c9c147e Ilias Tsitsimpis
    NoUserName, NoUUID, BadValue, QuotaLimit, InvalidResponse, NoEndpoints
51 7b5a37fd Ilias Tsitsimpis
52 7b5a37fd Ilias Tsitsimpis
53 7b5a37fd Ilias Tsitsimpis
# --------------------------------------------------------------------
54 4490d7b5 Ilias Tsitsimpis
# Astakos Client Class
55 bc5032a4 Ilias Tsitsimpis
56 794c94e6 Ilias Tsitsimpis
def get_token_from_cookie(request, cookie_name):
57 bc5032a4 Ilias Tsitsimpis
    """Extract token from the cookie name provided
58 bc5032a4 Ilias Tsitsimpis

59 bc5032a4 Ilias Tsitsimpis
    Cookie should be in the same form as astakos
60 bc5032a4 Ilias Tsitsimpis
    service sets its cookie contents:
61 bc5032a4 Ilias Tsitsimpis
        <user_uniq>|<user_token>
62 bc5032a4 Ilias Tsitsimpis

63 bc5032a4 Ilias Tsitsimpis
    """
64 bc5032a4 Ilias Tsitsimpis
    try:
65 bc5032a4 Ilias Tsitsimpis
        cookie_content = urllib.unquote(request.COOKIE.get(cookie_name, None))
66 bc5032a4 Ilias Tsitsimpis
        return cookie_content.split("|")[1]
67 2c9c147e Ilias Tsitsimpis
    except BaseException:
68 bc5032a4 Ilias Tsitsimpis
        return None
69 bc5032a4 Ilias Tsitsimpis
70 bc5032a4 Ilias Tsitsimpis
71 2c9c147e Ilias Tsitsimpis
# Too many instance attributes. pylint: disable-msg=R0902
72 2c9c147e Ilias Tsitsimpis
# Too many public methods. pylint: disable-msg=R0904
73 2c9c147e Ilias Tsitsimpis
class AstakosClient(object):
74 4490d7b5 Ilias Tsitsimpis
    """AstakosClient Class Implementation"""
75 4490d7b5 Ilias Tsitsimpis
76 4490d7b5 Ilias Tsitsimpis
    # ----------------------------------
77 2c9c147e Ilias Tsitsimpis
    # Initialize AstakosClient Class
78 2c9c147e Ilias Tsitsimpis
    # Too many arguments. pylint: disable-msg=R0913
79 2c9c147e Ilias Tsitsimpis
    # Too many local variables. pylint: disable-msg=R0914
80 2c9c147e Ilias Tsitsimpis
    # Too many statements. pylint: disable-msg=R0915
81 2c9c147e Ilias Tsitsimpis
    def __init__(self, token, auth_url,
82 2c9c147e Ilias Tsitsimpis
                 retry=0, use_pool=False, pool_size=8, logger=None):
83 c4644612 Ilias Tsitsimpis
        """Initialize AstakosClient Class
84 4490d7b5 Ilias Tsitsimpis

85 4490d7b5 Ilias Tsitsimpis
        Keyword arguments:
86 2c9c147e Ilias Tsitsimpis
        token       -- user's/service's token (string)
87 2c9c147e Ilias Tsitsimpis
        auth_url    -- i.e https://accounts.example.com/identity/v2.0
88 949baf4d Ilias Tsitsimpis
        retry       -- how many time to retry (integer)
89 2c9c147e Ilias Tsitsimpis
        use_pool    -- use objpool for http requests (boolean)
90 2c9c147e Ilias Tsitsimpis
        pool_size   -- if using pool, define the pool size
91 4490d7b5 Ilias Tsitsimpis
        logger      -- pass a different logger
92 4490d7b5 Ilias Tsitsimpis

93 4490d7b5 Ilias Tsitsimpis
        """
94 2c9c147e Ilias Tsitsimpis
95 2c9c147e Ilias Tsitsimpis
        # Get logger
96 4490d7b5 Ilias Tsitsimpis
        if logger is None:
97 4490d7b5 Ilias Tsitsimpis
            logger = logging.getLogger("astakosclient")
98 e6a61e6a Ilias Tsitsimpis
            logger.setLevel(logging.INFO)
99 2c9c147e Ilias Tsitsimpis
        logger.debug("Intialize AstakosClient: auth_url = %s, "
100 2c9c147e Ilias Tsitsimpis
                     "use_pool = %s, pool_size = %s",
101 2c9c147e Ilias Tsitsimpis
                     auth_url, use_pool, pool_size)
102 4490d7b5 Ilias Tsitsimpis
103 2c9c147e Ilias Tsitsimpis
        # Check that token and auth_url (mandatory options) are given
104 2c9c147e Ilias Tsitsimpis
        check_input("__init__", logger, token=token, auth_url=auth_url)
105 4490d7b5 Ilias Tsitsimpis
106 2c9c147e Ilias Tsitsimpis
        # Initialize connection class
107 2c9c147e Ilias Tsitsimpis
        parsed_auth_url = urlparse.urlparse(auth_url)
108 2c9c147e Ilias Tsitsimpis
        conn_class = \
109 2c9c147e Ilias Tsitsimpis
            scheme_to_class(parsed_auth_url.scheme, use_pool, pool_size)
110 e169a337 Ilias Tsitsimpis
        if conn_class is None:
111 2c9c147e Ilias Tsitsimpis
            msg = "Unsupported scheme: %s" % parsed_auth_url.scheme
112 2c9c147e Ilias Tsitsimpis
            logger.error(msg)
113 2c9c147e Ilias Tsitsimpis
            raise BadValue(msg)
114 4490d7b5 Ilias Tsitsimpis
115 2c9c147e Ilias Tsitsimpis
        # Save astakos base url, logger, connection class etc in our class
116 949baf4d Ilias Tsitsimpis
        self.retry = retry
117 4490d7b5 Ilias Tsitsimpis
        self.logger = logger
118 2c9c147e Ilias Tsitsimpis
        self.token = token
119 2c9c147e Ilias Tsitsimpis
        self.astakos_base_url = parsed_auth_url.netloc
120 2c9c147e Ilias Tsitsimpis
        self.scheme = parsed_auth_url.scheme
121 e169a337 Ilias Tsitsimpis
        self.conn_class = conn_class
122 4490d7b5 Ilias Tsitsimpis
123 2c9c147e Ilias Tsitsimpis
        # Initialize astakos api prefixes
124 2c9c147e Ilias Tsitsimpis
        # API urls under auth_url
125 2c9c147e Ilias Tsitsimpis
        self.auth_prefix = parsed_auth_url.path
126 2c9c147e Ilias Tsitsimpis
        self.api_tokens = join_urls(self.auth_prefix, "tokens")
127 2c9c147e Ilias Tsitsimpis
128 6476ceb7 Ilias Tsitsimpis
    def _fill_endpoints(self, endpoints, extra=False):
129 6476ceb7 Ilias Tsitsimpis
        """Fill the endpoints for our AstakosClient
130 6476ceb7 Ilias Tsitsimpis

131 6476ceb7 Ilias Tsitsimpis
        This will be done once (lazily) and the endpoints will be there
132 6476ceb7 Ilias Tsitsimpis
        to be used afterwards.
133 6476ceb7 Ilias Tsitsimpis
        The `extra' parameter is there for compatibility reasons. We are going
134 6476ceb7 Ilias Tsitsimpis
        to fill the oauth2 endpoint only if we need it. This way we are keeping
135 6476ceb7 Ilias Tsitsimpis
        astakosclient compatible with older Astakos version.
136 6476ceb7 Ilias Tsitsimpis

137 6476ceb7 Ilias Tsitsimpis
        """
138 45c0bcf8 Giorgos Korfiatis
        astakos_service_catalog = parse_endpoints(
139 2c9c147e Ilias Tsitsimpis
            endpoints, ep_name="astakos_account", ep_version_id="v1.0")
140 45c0bcf8 Giorgos Korfiatis
        self._account_url = \
141 45c0bcf8 Giorgos Korfiatis
            astakos_service_catalog[0]['endpoints'][0]['publicURL']
142 45c0bcf8 Giorgos Korfiatis
        parsed_account_url = urlparse.urlparse(self._account_url)
143 45c0bcf8 Giorgos Korfiatis
144 45c0bcf8 Giorgos Korfiatis
        self._account_prefix = parsed_account_url.path
145 45c0bcf8 Giorgos Korfiatis
        self.logger.debug("Got account_prefix \"%s\"" % self._account_prefix)
146 45c0bcf8 Giorgos Korfiatis
147 45c0bcf8 Giorgos Korfiatis
        self._ui_url = \
148 45c0bcf8 Giorgos Korfiatis
            astakos_service_catalog[0]['endpoints'][0]['SNF:uiURL']
149 45c0bcf8 Giorgos Korfiatis
        parsed_ui_url = urlparse.urlparse(self._ui_url)
150 45c0bcf8 Giorgos Korfiatis
151 45c0bcf8 Giorgos Korfiatis
        self._ui_prefix = parsed_ui_url.path
152 45c0bcf8 Giorgos Korfiatis
        self.logger.debug("Got ui_prefix \"%s\"" % self._ui_prefix)
153 45c0bcf8 Giorgos Korfiatis
154 6476ceb7 Ilias Tsitsimpis
        if extra:
155 6476ceb7 Ilias Tsitsimpis
            oauth2_service_catalog = \
156 6476ceb7 Ilias Tsitsimpis
                parse_endpoints(endpoints, ep_name="astakos_oauth2")
157 6476ceb7 Ilias Tsitsimpis
            self._oauth2_url = \
158 6476ceb7 Ilias Tsitsimpis
                oauth2_service_catalog[0]['endpoints'][0]['publicURL']
159 6476ceb7 Ilias Tsitsimpis
            parsed_oauth2_url = urlparse.urlparse(self._oauth2_url)
160 6476ceb7 Ilias Tsitsimpis
            self._oauth2_prefix = parsed_oauth2_url.path
161 d2104099 Sofia Papagiannaki
162 6476ceb7 Ilias Tsitsimpis
    def _get_value(self, s, extra=False):
163 45c0bcf8 Giorgos Korfiatis
        assert s in ['_account_url', '_account_prefix',
164 d2104099 Sofia Papagiannaki
                     '_ui_url', '_ui_prefix',
165 fe7d0186 Sofia Papagiannaki
                     '_oauth2_url', '_oauth2_prefix']
166 45c0bcf8 Giorgos Korfiatis
        try:
167 45c0bcf8 Giorgos Korfiatis
            return getattr(self, s)
168 45c0bcf8 Giorgos Korfiatis
        except AttributeError:
169 6476ceb7 Ilias Tsitsimpis
            self.get_endpoints(extra=extra)
170 45c0bcf8 Giorgos Korfiatis
            return getattr(self, s)
171 45c0bcf8 Giorgos Korfiatis
172 45c0bcf8 Giorgos Korfiatis
    @property
173 45c0bcf8 Giorgos Korfiatis
    def account_url(self):
174 45c0bcf8 Giorgos Korfiatis
        return self._get_value('_account_url')
175 45c0bcf8 Giorgos Korfiatis
176 45c0bcf8 Giorgos Korfiatis
    @property
177 45c0bcf8 Giorgos Korfiatis
    def account_prefix(self):
178 45c0bcf8 Giorgos Korfiatis
        return self._get_value('_account_prefix')
179 45c0bcf8 Giorgos Korfiatis
180 45c0bcf8 Giorgos Korfiatis
    @property
181 45c0bcf8 Giorgos Korfiatis
    def ui_url(self):
182 45c0bcf8 Giorgos Korfiatis
        return self._get_value('_ui_url')
183 45c0bcf8 Giorgos Korfiatis
184 45c0bcf8 Giorgos Korfiatis
    @property
185 45c0bcf8 Giorgos Korfiatis
    def ui_prefix(self):
186 45c0bcf8 Giorgos Korfiatis
        return self._get_value('_ui_prefix')
187 45c0bcf8 Giorgos Korfiatis
188 45c0bcf8 Giorgos Korfiatis
    @property
189 fe7d0186 Sofia Papagiannaki
    def oauth2_url(self):
190 6476ceb7 Ilias Tsitsimpis
        return self._get_value('_oauth2_url', extra=True)
191 d2104099 Sofia Papagiannaki
192 d2104099 Sofia Papagiannaki
    @property
193 fe7d0186 Sofia Papagiannaki
    def oauth2_prefix(self):
194 6476ceb7 Ilias Tsitsimpis
        return self._get_value('_oauth2_prefix', extra=True)
195 d2104099 Sofia Papagiannaki
196 d2104099 Sofia Papagiannaki
    @property
197 45c0bcf8 Giorgos Korfiatis
    def api_usercatalogs(self):
198 45c0bcf8 Giorgos Korfiatis
        return join_urls(self.account_prefix, "user_catalogs")
199 45c0bcf8 Giorgos Korfiatis
200 45c0bcf8 Giorgos Korfiatis
    @property
201 45c0bcf8 Giorgos Korfiatis
    def api_service_usercatalogs(self):
202 45c0bcf8 Giorgos Korfiatis
        return join_urls(self.account_prefix, "service/user_catalogs")
203 45c0bcf8 Giorgos Korfiatis
204 45c0bcf8 Giorgos Korfiatis
    @property
205 45c0bcf8 Giorgos Korfiatis
    def api_resources(self):
206 45c0bcf8 Giorgos Korfiatis
        return join_urls(self.account_prefix, "resources")
207 45c0bcf8 Giorgos Korfiatis
208 45c0bcf8 Giorgos Korfiatis
    @property
209 45c0bcf8 Giorgos Korfiatis
    def api_quotas(self):
210 45c0bcf8 Giorgos Korfiatis
        return join_urls(self.account_prefix, "quotas")
211 45c0bcf8 Giorgos Korfiatis
212 45c0bcf8 Giorgos Korfiatis
    @property
213 45c0bcf8 Giorgos Korfiatis
    def api_service_quotas(self):
214 45c0bcf8 Giorgos Korfiatis
        return join_urls(self.account_prefix, "service_quotas")
215 2c9c147e Ilias Tsitsimpis
216 45c0bcf8 Giorgos Korfiatis
    @property
217 45c0bcf8 Giorgos Korfiatis
    def api_commissions(self):
218 45c0bcf8 Giorgos Korfiatis
        return join_urls(self.account_prefix, "commissions")
219 2c9c147e Ilias Tsitsimpis
220 45c0bcf8 Giorgos Korfiatis
    @property
221 45c0bcf8 Giorgos Korfiatis
    def api_commissions_action(self):
222 45c0bcf8 Giorgos Korfiatis
        return join_urls(self.api_commissions, "action")
223 45c0bcf8 Giorgos Korfiatis
224 45c0bcf8 Giorgos Korfiatis
    @property
225 45c0bcf8 Giorgos Korfiatis
    def api_feedback(self):
226 45c0bcf8 Giorgos Korfiatis
        return join_urls(self.account_prefix, "feedback")
227 45c0bcf8 Giorgos Korfiatis
228 45c0bcf8 Giorgos Korfiatis
    @property
229 45c0bcf8 Giorgos Korfiatis
    def api_projects(self):
230 45c0bcf8 Giorgos Korfiatis
        return join_urls(self.account_prefix, "projects")
231 45c0bcf8 Giorgos Korfiatis
232 45c0bcf8 Giorgos Korfiatis
    @property
233 45c0bcf8 Giorgos Korfiatis
    def api_applications(self):
234 45c0bcf8 Giorgos Korfiatis
        return join_urls(self.api_projects, "apps")
235 45c0bcf8 Giorgos Korfiatis
236 45c0bcf8 Giorgos Korfiatis
    @property
237 45c0bcf8 Giorgos Korfiatis
    def api_memberships(self):
238 45c0bcf8 Giorgos Korfiatis
        return join_urls(self.api_projects, "memberships")
239 45c0bcf8 Giorgos Korfiatis
240 45c0bcf8 Giorgos Korfiatis
    @property
241 45c0bcf8 Giorgos Korfiatis
    def api_getservices(self):
242 45c0bcf8 Giorgos Korfiatis
        return join_urls(self.ui_prefix, "get_services")
243 2c9c147e Ilias Tsitsimpis
244 d2104099 Sofia Papagiannaki
    @property
245 fe7d0186 Sofia Papagiannaki
    def api_oauth2_auth(self):
246 fe7d0186 Sofia Papagiannaki
        return join_urls(self.oauth2_prefix, "auth")
247 d2104099 Sofia Papagiannaki
248 d2104099 Sofia Papagiannaki
    @property
249 fe7d0186 Sofia Papagiannaki
    def api_oauth2_token(self):
250 fe7d0186 Sofia Papagiannaki
        return join_urls(self.oauth2_prefix, "token")
251 d2104099 Sofia Papagiannaki
252 4490d7b5 Ilias Tsitsimpis
    # ----------------------------------
253 2c9c147e Ilias Tsitsimpis
    @retry_dec
254 2c9c147e Ilias Tsitsimpis
    def _call_astakos(self, request_path, headers=None,
255 25a04cdd Ilias Tsitsimpis
                      body=None, method="GET", log_body=True):
256 4490d7b5 Ilias Tsitsimpis
        """Make the actual call to Astakos Service"""
257 2c9c147e Ilias Tsitsimpis
        hashed_token = hashlib.sha1()
258 2c9c147e Ilias Tsitsimpis
        hashed_token.update(self.token)
259 4490d7b5 Ilias Tsitsimpis
        self.logger.debug(
260 2c9c147e Ilias Tsitsimpis
            "Make a %s request to %s, using token with hash %s, "
261 2c9c147e Ilias Tsitsimpis
            "with headers %s and body %s",
262 2c9c147e Ilias Tsitsimpis
            method, request_path, hashed_token.hexdigest(), headers,
263 2c9c147e Ilias Tsitsimpis
            body if log_body else "(not logged)")
264 4490d7b5 Ilias Tsitsimpis
265 98752f06 Ilias Tsitsimpis
        # Check Input
266 8f2d7ede Ilias Tsitsimpis
        if headers is None:
267 8f2d7ede Ilias Tsitsimpis
            headers = {}
268 8f2d7ede Ilias Tsitsimpis
        if body is None:
269 8f2d7ede Ilias Tsitsimpis
            body = {}
270 2cd636fe Stavros Sachtouris
        # Initialize log_request and log_response attributes
271 2cd636fe Stavros Sachtouris
        self.log_request = None
272 2cd636fe Stavros Sachtouris
        self.log_response = None
273 98752f06 Ilias Tsitsimpis
274 4490d7b5 Ilias Tsitsimpis
        # Build request's header and body
275 4490d7b5 Ilias Tsitsimpis
        kwargs = {}
276 8f2d7ede Ilias Tsitsimpis
        kwargs['headers'] = copy(headers)
277 2c9c147e Ilias Tsitsimpis
        kwargs['headers']['X-Auth-Token'] = self.token
278 4490d7b5 Ilias Tsitsimpis
        if body:
279 8f2d7ede Ilias Tsitsimpis
            kwargs['body'] = copy(body)
280 4490d7b5 Ilias Tsitsimpis
            kwargs['headers'].setdefault(
281 4490d7b5 Ilias Tsitsimpis
                'content-type', 'application/octet-stream')
282 4490d7b5 Ilias Tsitsimpis
        kwargs['headers'].setdefault('content-length',
283 4490d7b5 Ilias Tsitsimpis
                                     len(body) if body else 0)
284 4490d7b5 Ilias Tsitsimpis
285 4490d7b5 Ilias Tsitsimpis
        try:
286 6837f014 Ilias Tsitsimpis
            # Get the connection object
287 2c9c147e Ilias Tsitsimpis
            with self.conn_class(self.astakos_base_url) as conn:
288 2cd636fe Stavros Sachtouris
                # Log the request so other clients (like kamaki)
289 2cd636fe Stavros Sachtouris
                # can use them to produce their own log messages.
290 2cd636fe Stavros Sachtouris
                self.log_request = dict(method=method, path=request_path)
291 2cd636fe Stavros Sachtouris
                self.log_request.update(kwargs)
292 2cd636fe Stavros Sachtouris
293 6837f014 Ilias Tsitsimpis
                # Send request
294 2c9c147e Ilias Tsitsimpis
                # Used * or ** magic. pylint: disable-msg=W0142
295 21190887 Ilias Tsitsimpis
                (message, data, status) = \
296 2c9c147e Ilias Tsitsimpis
                    _do_request(conn, method, request_path, **kwargs)
297 2cd636fe Stavros Sachtouris
298 2cd636fe Stavros Sachtouris
                # Log the response so other clients (like kamaki)
299 2cd636fe Stavros Sachtouris
                # can use them to produce their own log messages.
300 2cd636fe Stavros Sachtouris
                self.log_response = dict(
301 2cd636fe Stavros Sachtouris
                    status=status, message=message, data=data)
302 4490d7b5 Ilias Tsitsimpis
        except Exception as err:
303 996061fa Ilias Tsitsimpis
            self.logger.error("Failed to send request: %s" % repr(err))
304 4490d7b5 Ilias Tsitsimpis
            raise AstakosClientException(str(err))
305 4490d7b5 Ilias Tsitsimpis
306 4490d7b5 Ilias Tsitsimpis
        # Return
307 4490d7b5 Ilias Tsitsimpis
        self.logger.debug("Request returned with status %s" % status)
308 8fe6475a Ilias Tsitsimpis
        if status == 400:
309 21190887 Ilias Tsitsimpis
            raise BadRequest(message, data)
310 be284f6a Christos Stavrakakis
        elif status == 401:
311 21190887 Ilias Tsitsimpis
            raise Unauthorized(message, data)
312 be284f6a Christos Stavrakakis
        elif status == 403:
313 21190887 Ilias Tsitsimpis
            raise Forbidden(message, data)
314 be284f6a Christos Stavrakakis
        elif status == 404:
315 21190887 Ilias Tsitsimpis
            raise NotFound(message, data)
316 be284f6a Christos Stavrakakis
        elif status < 200 or status >= 300:
317 21190887 Ilias Tsitsimpis
            raise AstakosClientException(message, data, status)
318 0a2a342c Ilias Tsitsimpis
319 0a2a342c Ilias Tsitsimpis
        try:
320 0a2a342c Ilias Tsitsimpis
            if data:
321 0a2a342c Ilias Tsitsimpis
                return simplejson.loads(unicode(data))
322 0a2a342c Ilias Tsitsimpis
            else:
323 10797183 Ilias Tsitsimpis
                return None
324 0a2a342c Ilias Tsitsimpis
        except Exception as err:
325 8975f6f6 Christos Stavrakakis
            msg = "Cannot parse response \"%s\" with simplejson: %s"
326 8975f6f6 Christos Stavrakakis
            self.logger.error(msg % (data, str(err)))
327 0a2a342c Ilias Tsitsimpis
            raise InvalidResponse(str(err), data)
328 4490d7b5 Ilias Tsitsimpis
329 4490d7b5 Ilias Tsitsimpis
    # ----------------------------------
330 7b5a37fd Ilias Tsitsimpis
    # do a POST to ``API_USERCATALOGS`` (or ``API_SERVICE_USERCATALOGS``)
331 c4644612 Ilias Tsitsimpis
    #   with {'uuids': uuids}
332 2c9c147e Ilias Tsitsimpis
    def _uuid_catalog(self, uuids, req_path):
333 2c9c147e Ilias Tsitsimpis
        """Helper function to retrieve uuid catalog"""
334 1c26b500 Ilias Tsitsimpis
        req_headers = {'content-type': 'application/json'}
335 19198628 Ilias Tsitsimpis
        req_body = parse_request({'uuids': uuids}, self.logger)
336 2c9c147e Ilias Tsitsimpis
        data = self._call_astakos(req_path, headers=req_headers,
337 2c9c147e Ilias Tsitsimpis
                                  body=req_body, method="POST")
338 2377e7c2 Ilias Tsitsimpis
        if "uuid_catalog" in data:
339 2377e7c2 Ilias Tsitsimpis
            return data.get("uuid_catalog")
340 2377e7c2 Ilias Tsitsimpis
        else:
341 2c9c147e Ilias Tsitsimpis
            msg = "_uuid_catalog request returned %s. No uuid_catalog found" \
342 2c9c147e Ilias Tsitsimpis
                  % data
343 2c9c147e Ilias Tsitsimpis
            self.logger.error(msg)
344 2c9c147e Ilias Tsitsimpis
            raise AstakosClientException(msg)
345 1c26b500 Ilias Tsitsimpis
346 2c9c147e Ilias Tsitsimpis
    def get_usernames(self, uuids):
347 4490d7b5 Ilias Tsitsimpis
        """Return a uuid_catalog dictionary for the given uuids
348 4490d7b5 Ilias Tsitsimpis

349 4490d7b5 Ilias Tsitsimpis
        Keyword arguments:
350 4490d7b5 Ilias Tsitsimpis
        uuids   -- list of user ids (list of strings)
351 4490d7b5 Ilias Tsitsimpis

352 4490d7b5 Ilias Tsitsimpis
        The returned uuid_catalog is a dictionary with uuids as
353 4490d7b5 Ilias Tsitsimpis
        keys and the corresponding user names as values
354 4490d7b5 Ilias Tsitsimpis

355 4490d7b5 Ilias Tsitsimpis
        """
356 2c9c147e Ilias Tsitsimpis
        return self._uuid_catalog(uuids, self.api_usercatalogs)
357 4490d7b5 Ilias Tsitsimpis
358 2c9c147e Ilias Tsitsimpis
    def get_username(self, uuid):
359 794c94e6 Ilias Tsitsimpis
        """Return the user name of a uuid (see get_usernames)"""
360 10797183 Ilias Tsitsimpis
        check_input("get_username", self.logger, uuid=uuid)
361 2c9c147e Ilias Tsitsimpis
        uuid_dict = self.get_usernames([uuid])
362 2377e7c2 Ilias Tsitsimpis
        if uuid in uuid_dict:
363 2377e7c2 Ilias Tsitsimpis
            return uuid_dict.get(uuid)
364 2377e7c2 Ilias Tsitsimpis
        else:
365 794c94e6 Ilias Tsitsimpis
            raise NoUserName(uuid)
366 4490d7b5 Ilias Tsitsimpis
367 2c9c147e Ilias Tsitsimpis
    def service_get_usernames(self, uuids):
368 1c26b500 Ilias Tsitsimpis
        """Return a uuid_catalog dict using a service's token"""
369 2c9c147e Ilias Tsitsimpis
        return self._uuid_catalog(uuids, self.api_service_usercatalogs)
370 1c26b500 Ilias Tsitsimpis
371 2c9c147e Ilias Tsitsimpis
    def service_get_username(self, uuid):
372 1c26b500 Ilias Tsitsimpis
        """Return the displayName of a uuid using a service's token"""
373 10797183 Ilias Tsitsimpis
        check_input("service_get_username", self.logger, uuid=uuid)
374 2c9c147e Ilias Tsitsimpis
        uuid_dict = self.service_get_usernames([uuid])
375 2377e7c2 Ilias Tsitsimpis
        if uuid in uuid_dict:
376 2377e7c2 Ilias Tsitsimpis
            return uuid_dict.get(uuid)
377 2377e7c2 Ilias Tsitsimpis
        else:
378 794c94e6 Ilias Tsitsimpis
            raise NoUserName(uuid)
379 1c26b500 Ilias Tsitsimpis
380 aaf0a42c Ilias Tsitsimpis
    # ----------------------------------
381 7b5a37fd Ilias Tsitsimpis
    # do a POST to ``API_USERCATALOGS`` (or ``API_SERVICE_USERCATALOGS``)
382 c4644612 Ilias Tsitsimpis
    #   with {'displaynames': display_names}
383 2c9c147e Ilias Tsitsimpis
    def _displayname_catalog(self, display_names, req_path):
384 2c9c147e Ilias Tsitsimpis
        """Helper function to retrieve display names catalog"""
385 1c26b500 Ilias Tsitsimpis
        req_headers = {'content-type': 'application/json'}
386 19198628 Ilias Tsitsimpis
        req_body = parse_request({'displaynames': display_names}, self.logger)
387 2c9c147e Ilias Tsitsimpis
        data = self._call_astakos(req_path, headers=req_headers,
388 2c9c147e Ilias Tsitsimpis
                                  body=req_body, method="POST")
389 2377e7c2 Ilias Tsitsimpis
        if "displayname_catalog" in data:
390 2377e7c2 Ilias Tsitsimpis
            return data.get("displayname_catalog")
391 2377e7c2 Ilias Tsitsimpis
        else:
392 2c9c147e Ilias Tsitsimpis
            msg = "_displayname_catalog request returned %s. " \
393 2c9c147e Ilias Tsitsimpis
                  "No displayname_catalog found" % data
394 2c9c147e Ilias Tsitsimpis
            self.logger.error(msg)
395 2c9c147e Ilias Tsitsimpis
            raise AstakosClientException(msg)
396 1c26b500 Ilias Tsitsimpis
397 2c9c147e Ilias Tsitsimpis
    def get_uuids(self, display_names):
398 aaf0a42c Ilias Tsitsimpis
        """Return a displayname_catalog for the given names
399 aaf0a42c Ilias Tsitsimpis

400 aaf0a42c Ilias Tsitsimpis
        Keyword arguments:
401 aaf0a42c Ilias Tsitsimpis
        display_names   -- list of user names (list of strings)
402 aaf0a42c Ilias Tsitsimpis

403 aaf0a42c Ilias Tsitsimpis
        The returned displayname_catalog is a dictionary with
404 aaf0a42c Ilias Tsitsimpis
        the names as keys and the corresponding uuids as values
405 aaf0a42c Ilias Tsitsimpis

406 aaf0a42c Ilias Tsitsimpis
        """
407 2c9c147e Ilias Tsitsimpis
        return self._displayname_catalog(
408 2c9c147e Ilias Tsitsimpis
            display_names, self.api_usercatalogs)
409 aaf0a42c Ilias Tsitsimpis
410 2c9c147e Ilias Tsitsimpis
    def get_uuid(self, display_name):
411 aaf0a42c Ilias Tsitsimpis
        """Return the uuid of a name (see getUUIDs)"""
412 10797183 Ilias Tsitsimpis
        check_input("get_uuid", self.logger, display_name=display_name)
413 2c9c147e Ilias Tsitsimpis
        name_dict = self.get_uuids([display_name])
414 2377e7c2 Ilias Tsitsimpis
        if display_name in name_dict:
415 2377e7c2 Ilias Tsitsimpis
            return name_dict.get(display_name)
416 2377e7c2 Ilias Tsitsimpis
        else:
417 2377e7c2 Ilias Tsitsimpis
            raise NoUUID(display_name)
418 aaf0a42c Ilias Tsitsimpis
419 2c9c147e Ilias Tsitsimpis
    def service_get_uuids(self, display_names):
420 1c26b500 Ilias Tsitsimpis
        """Return a display_name catalog using a service's token"""
421 2c9c147e Ilias Tsitsimpis
        return self._displayname_catalog(
422 2c9c147e Ilias Tsitsimpis
            display_names, self.api_service_usercatalogs)
423 1c26b500 Ilias Tsitsimpis
424 2c9c147e Ilias Tsitsimpis
    def service_get_uuid(self, display_name):
425 1c26b500 Ilias Tsitsimpis
        """Return the uuid of a name using a service's token"""
426 10797183 Ilias Tsitsimpis
        check_input("service_get_uuid", self.logger, display_name=display_name)
427 2c9c147e Ilias Tsitsimpis
        name_dict = self.service_get_uuids([display_name])
428 2377e7c2 Ilias Tsitsimpis
        if display_name in name_dict:
429 2377e7c2 Ilias Tsitsimpis
            return name_dict.get(display_name)
430 2377e7c2 Ilias Tsitsimpis
        else:
431 2377e7c2 Ilias Tsitsimpis
            raise NoUUID(display_name)
432 1c26b500 Ilias Tsitsimpis
433 3f8d6b11 Ilias Tsitsimpis
    # ----------------------------------
434 7b5a37fd Ilias Tsitsimpis
    # do a GET to ``API_GETSERVICES``
435 794c94e6 Ilias Tsitsimpis
    def get_services(self):
436 3f8d6b11 Ilias Tsitsimpis
        """Return a list of dicts with the registered services"""
437 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(self.api_getservices)
438 c4644612 Ilias Tsitsimpis
439 c4644612 Ilias Tsitsimpis
    # ----------------------------------
440 7b5a37fd Ilias Tsitsimpis
    # do a GET to ``API_RESOURCES``
441 c4644612 Ilias Tsitsimpis
    def get_resources(self):
442 c4644612 Ilias Tsitsimpis
        """Return a dict of dicts with the available resources"""
443 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(self.api_resources)
444 3f8d6b11 Ilias Tsitsimpis
445 baeb2ba5 Ilias Tsitsimpis
    # ----------------------------------
446 92683993 Ilias Tsitsimpis
    # do a POST to ``API_FEEDBACK``
447 2c9c147e Ilias Tsitsimpis
    def send_feedback(self, message, data):
448 92683993 Ilias Tsitsimpis
        """Send feedback to astakos service
449 92683993 Ilias Tsitsimpis

450 92683993 Ilias Tsitsimpis
        keyword arguments:
451 92683993 Ilias Tsitsimpis
        message     -- Feedback message
452 92683993 Ilias Tsitsimpis
        data        -- Additional information about service client status
453 92683993 Ilias Tsitsimpis

454 92683993 Ilias Tsitsimpis
        In case of success return nothing.
455 92683993 Ilias Tsitsimpis
        Otherwise raise an AstakosClientException
456 92683993 Ilias Tsitsimpis

457 92683993 Ilias Tsitsimpis
        """
458 92683993 Ilias Tsitsimpis
        check_input("send_feedback", self.logger, message=message, data=data)
459 92683993 Ilias Tsitsimpis
        req_body = urllib.urlencode(
460 92683993 Ilias Tsitsimpis
            {'feedback_msg': message, 'feedback_data': data})
461 2c9c147e Ilias Tsitsimpis
        self._call_astakos(self.api_feedback, headers=None,
462 2c9c147e Ilias Tsitsimpis
                           body=req_body, method="POST")
463 108be31f Ilias Tsitsimpis
464 45c0bcf8 Giorgos Korfiatis
    # -----------------------------------------
465 45c0bcf8 Giorgos Korfiatis
    # do a POST to ``API_TOKENS`` with no token
466 6476ceb7 Ilias Tsitsimpis
    def get_endpoints(self, extra=False):
467 45c0bcf8 Giorgos Korfiatis
        """ Get services' endpoints
468 45c0bcf8 Giorgos Korfiatis

469 6476ceb7 Ilias Tsitsimpis
        The extra parameter is to be used by _fill_endpoints.
470 45c0bcf8 Giorgos Korfiatis
        In case of error raise an AstakosClientException.
471 45c0bcf8 Giorgos Korfiatis

472 45c0bcf8 Giorgos Korfiatis
        """
473 45c0bcf8 Giorgos Korfiatis
        req_headers = {'content-type': 'application/json'}
474 45c0bcf8 Giorgos Korfiatis
        req_body = None
475 45c0bcf8 Giorgos Korfiatis
        r = self._call_astakos(self.api_tokens, headers=req_headers,
476 45c0bcf8 Giorgos Korfiatis
                               body=req_body, method="POST",
477 45c0bcf8 Giorgos Korfiatis
                               log_body=False)
478 6476ceb7 Ilias Tsitsimpis
        self._fill_endpoints(r, extra=extra)
479 45c0bcf8 Giorgos Korfiatis
        return r
480 45c0bcf8 Giorgos Korfiatis
481 45c0bcf8 Giorgos Korfiatis
    # --------------------------------------
482 45c0bcf8 Giorgos Korfiatis
    # do a POST to ``API_TOKENS`` with a token
483 45c0bcf8 Giorgos Korfiatis
    def authenticate(self, tenant_name=None):
484 2c9c147e Ilias Tsitsimpis
        """ Authenticate and get services' endpoints
485 25a04cdd Ilias Tsitsimpis

486 25a04cdd Ilias Tsitsimpis
        Keyword arguments:
487 2c9c147e Ilias Tsitsimpis
        tenant_name         -- user's uniq id (optional)
488 25a04cdd Ilias Tsitsimpis

489 25a04cdd Ilias Tsitsimpis
        It returns back the token as well as information about the token
490 45c0bcf8 Giorgos Korfiatis
        holder and the services he/she can access (in json format).
491 2c9c147e Ilias Tsitsimpis

492 2c9c147e Ilias Tsitsimpis
        The tenant_name is optional and if it is given it must match the
493 2c9c147e Ilias Tsitsimpis
        user's uuid.
494 2c9c147e Ilias Tsitsimpis

495 25a04cdd Ilias Tsitsimpis
        In case of error raise an AstakosClientException.
496 25a04cdd Ilias Tsitsimpis

497 25a04cdd Ilias Tsitsimpis
        """
498 25a04cdd Ilias Tsitsimpis
        req_headers = {'content-type': 'application/json'}
499 45c0bcf8 Giorgos Korfiatis
        body = {'auth': {'token': {'id': self.token}}}
500 45c0bcf8 Giorgos Korfiatis
        if tenant_name is not None:
501 45c0bcf8 Giorgos Korfiatis
            body['auth']['tenantName'] = tenant_name
502 45c0bcf8 Giorgos Korfiatis
        req_body = parse_request(body, self.logger)
503 45c0bcf8 Giorgos Korfiatis
        r = self._call_astakos(self.api_tokens, headers=req_headers,
504 45c0bcf8 Giorgos Korfiatis
                               body=req_body, method="POST",
505 45c0bcf8 Giorgos Korfiatis
                               log_body=False)
506 45c0bcf8 Giorgos Korfiatis
        self._fill_endpoints(r)
507 45c0bcf8 Giorgos Korfiatis
        return r
508 25a04cdd Ilias Tsitsimpis
509 b39ca571 Sofia Papagiannaki
    # --------------------------------------
510 b39ca571 Sofia Papagiannaki
    # do a GET to ``API_TOKENS`` with a token
511 259a5f9a Ilias Tsitsimpis
    def validate_token(self, token_id, belongs_to=None):
512 b39ca571 Sofia Papagiannaki
        """ Validate a temporary access token (oath2)
513 b39ca571 Sofia Papagiannaki

514 b39ca571 Sofia Papagiannaki
        Keyword arguments:
515 b39ca571 Sofia Papagiannaki
        belongsTo         -- confirm that token belongs to tenant
516 b39ca571 Sofia Papagiannaki

517 b39ca571 Sofia Papagiannaki
        It returns back the token as well as information about the token
518 b39ca571 Sofia Papagiannaki
        holder.
519 b39ca571 Sofia Papagiannaki

520 259a5f9a Ilias Tsitsimpis
        The belongs_to is optional and if it is given it must be inside the
521 b39ca571 Sofia Papagiannaki
        token's scope.
522 b39ca571 Sofia Papagiannaki

523 b39ca571 Sofia Papagiannaki
        In case of error raise an AstakosClientException.
524 b39ca571 Sofia Papagiannaki

525 b39ca571 Sofia Papagiannaki
        """
526 b39ca571 Sofia Papagiannaki
        path = join_urls(self.api_tokens, str(token_id))
527 259a5f9a Ilias Tsitsimpis
        if belongs_to is not None:
528 259a5f9a Ilias Tsitsimpis
            params = {'belongsTo': belongs_to}
529 b39ca571 Sofia Papagiannaki
            path = '%s?%s' % (path, urllib.urlencode(params))
530 b39ca571 Sofia Papagiannaki
        return self._call_astakos(path, method="GET", log_body=False)
531 b39ca571 Sofia Papagiannaki
532 25a04cdd Ilias Tsitsimpis
    # ----------------------------------
533 7b5a37fd Ilias Tsitsimpis
    # do a GET to ``API_QUOTAS``
534 2c9c147e Ilias Tsitsimpis
    def get_quotas(self):
535 fd420756 Ilias Tsitsimpis
        """Get user's quotas
536 fd420756 Ilias Tsitsimpis

537 fd420756 Ilias Tsitsimpis
        In case of success return a dict of dicts with user's current quotas.
538 fd420756 Ilias Tsitsimpis
        Otherwise raise an AstakosClientException
539 fd420756 Ilias Tsitsimpis

540 fd420756 Ilias Tsitsimpis
        """
541 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(self.api_quotas)
542 baeb2ba5 Ilias Tsitsimpis
543 fd420756 Ilias Tsitsimpis
    # ----------------------------------
544 7b5a37fd Ilias Tsitsimpis
    # do a GET to ``API_SERVICE_QUOTAS``
545 2c9c147e Ilias Tsitsimpis
    def service_get_quotas(self, user=None):
546 5b33b8e5 Giorgos Korfiatis
        """Get all quotas for resources associated with the service
547 5b33b8e5 Giorgos Korfiatis

548 5b33b8e5 Giorgos Korfiatis
        Keyword arguments:
549 db9f7a2b Giorgos Korfiatis
        user    -- optionally, the uuid of a specific user
550 5b33b8e5 Giorgos Korfiatis

551 5b33b8e5 Giorgos Korfiatis
        In case of success return a dict of dicts of dicts with current quotas
552 db9f7a2b Giorgos Korfiatis
        for all users, or of a specified user, if user argument is set.
553 5b33b8e5 Giorgos Korfiatis
        Otherwise raise an AstakosClientException
554 5b33b8e5 Giorgos Korfiatis

555 5b33b8e5 Giorgos Korfiatis
        """
556 2c9c147e Ilias Tsitsimpis
        query = self.api_service_quotas
557 db9f7a2b Giorgos Korfiatis
        if user is not None:
558 db9f7a2b Giorgos Korfiatis
            query += "?user=" + user
559 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(query)
560 5b33b8e5 Giorgos Korfiatis
561 5b33b8e5 Giorgos Korfiatis
    # ----------------------------------
562 7b5a37fd Ilias Tsitsimpis
    # do a POST to ``API_COMMISSIONS``
563 da9bcceb Giorgos Korfiatis
    def _issue_commission(self, request):
564 fd420756 Ilias Tsitsimpis
        """Issue a commission
565 fd420756 Ilias Tsitsimpis

566 fd420756 Ilias Tsitsimpis
        Keyword arguments:
567 fd420756 Ilias Tsitsimpis
        request -- commision request (dict)
568 fd420756 Ilias Tsitsimpis

569 fd420756 Ilias Tsitsimpis
        In case of success return commission's id (int).
570 fd420756 Ilias Tsitsimpis
        Otherwise raise an AstakosClientException.
571 fd420756 Ilias Tsitsimpis

572 fd420756 Ilias Tsitsimpis
        """
573 fd420756 Ilias Tsitsimpis
        req_headers = {'content-type': 'application/json'}
574 19198628 Ilias Tsitsimpis
        req_body = parse_request(request, self.logger)
575 fd420756 Ilias Tsitsimpis
        try:
576 2c9c147e Ilias Tsitsimpis
            response = self._call_astakos(self.api_commissions,
577 2c9c147e Ilias Tsitsimpis
                                          headers=req_headers,
578 2c9c147e Ilias Tsitsimpis
                                          body=req_body,
579 2c9c147e Ilias Tsitsimpis
                                          method="POST")
580 fd420756 Ilias Tsitsimpis
        except AstakosClientException as err:
581 fd420756 Ilias Tsitsimpis
            if err.status == 413:
582 fd420756 Ilias Tsitsimpis
                raise QuotaLimit(err.message, err.details)
583 fd420756 Ilias Tsitsimpis
            else:
584 fd420756 Ilias Tsitsimpis
                raise
585 fd420756 Ilias Tsitsimpis
586 fd420756 Ilias Tsitsimpis
        if "serial" in response:
587 fd420756 Ilias Tsitsimpis
            return response['serial']
588 fd420756 Ilias Tsitsimpis
        else:
589 2c9c147e Ilias Tsitsimpis
            msg = "issue_commission_core request returned %s. " + \
590 2c9c147e Ilias Tsitsimpis
                  "No serial found" % response
591 2c9c147e Ilias Tsitsimpis
            self.logger.error(msg)
592 2c9c147e Ilias Tsitsimpis
            raise AstakosClientException(msg)
593 fd420756 Ilias Tsitsimpis
594 da9bcceb Giorgos Korfiatis
    def _mk_user_provision(self, holder, source, resource, quantity):
595 da9bcceb Giorgos Korfiatis
        holder = "user:" + holder
596 da9bcceb Giorgos Korfiatis
        source = "project:" + source
597 da9bcceb Giorgos Korfiatis
        return {"holder": holder, "source": source,
598 da9bcceb Giorgos Korfiatis
                "resource": resource, "quantity": quantity}
599 da9bcceb Giorgos Korfiatis
600 da9bcceb Giorgos Korfiatis
    def _mk_project_provision(self, holder, resource, quantity):
601 da9bcceb Giorgos Korfiatis
        holder = "project:" + holder
602 da9bcceb Giorgos Korfiatis
        return {"holder": holder, "source": None,
603 da9bcceb Giorgos Korfiatis
                "resource": resource, "quantity": quantity}
604 da9bcceb Giorgos Korfiatis
605 da9bcceb Giorgos Korfiatis
    def mk_provisions(self, holder, source, resource, quantity):
606 da9bcceb Giorgos Korfiatis
        return [self._mk_user_provision(holder, source, resource, quantity),
607 da9bcceb Giorgos Korfiatis
                self._mk_project_provision(source, resource, quantity)]
608 da9bcceb Giorgos Korfiatis
609 da9bcceb Giorgos Korfiatis
    def issue_commission_generic(self, user_provisions, project_provisions,
610 da9bcceb Giorgos Korfiatis
                                 name="", force=False, auto_accept=False):
611 da9bcceb Giorgos Korfiatis
        """Issue commission (for multiple holder/source pairs)
612 da9bcceb Giorgos Korfiatis

613 da9bcceb Giorgos Korfiatis
        keyword arguments:
614 da9bcceb Giorgos Korfiatis
        user_provisions  -- dict mapping user holdings
615 da9bcceb Giorgos Korfiatis
                            (user, project, resource) to integer quantities
616 da9bcceb Giorgos Korfiatis
        project_provisions -- dict mapping project holdings
617 da9bcceb Giorgos Korfiatis
                              (project, resource) to integer quantities
618 da9bcceb Giorgos Korfiatis
        name        -- description of the commission (string)
619 da9bcceb Giorgos Korfiatis
        force       -- force this commission (boolean)
620 da9bcceb Giorgos Korfiatis
        auto_accept -- auto accept this commission (boolean)
621 da9bcceb Giorgos Korfiatis

622 da9bcceb Giorgos Korfiatis
        In case of success return commission's id (int).
623 da9bcceb Giorgos Korfiatis
        Otherwise raise an AstakosClientException.
624 da9bcceb Giorgos Korfiatis

625 da9bcceb Giorgos Korfiatis
        """
626 da9bcceb Giorgos Korfiatis
        request = {}
627 da9bcceb Giorgos Korfiatis
        request["force"] = force
628 da9bcceb Giorgos Korfiatis
        request["auto_accept"] = auto_accept
629 da9bcceb Giorgos Korfiatis
        request["name"] = name
630 da9bcceb Giorgos Korfiatis
        try:
631 da9bcceb Giorgos Korfiatis
            request["provisions"] = []
632 da9bcceb Giorgos Korfiatis
            for (holder, source, resource), quantity in \
633 da9bcceb Giorgos Korfiatis
                    user_provisions.iteritems():
634 da9bcceb Giorgos Korfiatis
                p = self._mk_user_provision(holder, source, resource, quantity)
635 da9bcceb Giorgos Korfiatis
                request["provisions"].append(p)
636 da9bcceb Giorgos Korfiatis
            for (holder, resource), quantity in project_provisions.iteritems():
637 da9bcceb Giorgos Korfiatis
                p = self._mk_project_provision(holder, resource, quantity)
638 da9bcceb Giorgos Korfiatis
                request["provisions"].append(p)
639 da9bcceb Giorgos Korfiatis
        except Exception as err:
640 da9bcceb Giorgos Korfiatis
            self.logger.error(str(err))
641 da9bcceb Giorgos Korfiatis
            raise BadValue(str(err))
642 da9bcceb Giorgos Korfiatis
643 da9bcceb Giorgos Korfiatis
        return self._issue_commission(request)
644 da9bcceb Giorgos Korfiatis
645 2c9c147e Ilias Tsitsimpis
    def issue_one_commission(self, holder, source, provisions,
646 3a1bed03 Giorgos Korfiatis
                             name="", force=False, auto_accept=False):
647 12eab714 Ilias Tsitsimpis
        """Issue one commission (with specific holder and source)
648 12eab714 Ilias Tsitsimpis

649 12eab714 Ilias Tsitsimpis
        keyword arguments:
650 12eab714 Ilias Tsitsimpis
        holder      -- user's id (string)
651 12eab714 Ilias Tsitsimpis
        source      -- commission's source (ex system) (string)
652 8b68fa76 Giorgos Korfiatis
        provisions  -- resources with their quantity (dict from string to int)
653 3a1bed03 Giorgos Korfiatis
        name        -- description of the commission (string)
654 12eab714 Ilias Tsitsimpis
        force       -- force this commission (boolean)
655 12eab714 Ilias Tsitsimpis
        auto_accept -- auto accept this commission (boolean)
656 12eab714 Ilias Tsitsimpis

657 12eab714 Ilias Tsitsimpis
        In case of success return commission's id (int).
658 12eab714 Ilias Tsitsimpis
        Otherwise raise an AstakosClientException.
659 12eab714 Ilias Tsitsimpis

660 12eab714 Ilias Tsitsimpis
        """
661 d5f086f2 Ilias Tsitsimpis
        check_input("issue_one_commission", self.logger,
662 d5f086f2 Ilias Tsitsimpis
                    holder=holder, source=source,
663 d5f086f2 Ilias Tsitsimpis
                    provisions=provisions)
664 12eab714 Ilias Tsitsimpis
665 12eab714 Ilias Tsitsimpis
        request = {}
666 12eab714 Ilias Tsitsimpis
        request["force"] = force
667 12eab714 Ilias Tsitsimpis
        request["auto_accept"] = auto_accept
668 3a1bed03 Giorgos Korfiatis
        request["name"] = name
669 12eab714 Ilias Tsitsimpis
        try:
670 12eab714 Ilias Tsitsimpis
            request["provisions"] = []
671 567f49a2 Giorgos Korfiatis
            for resource, quantity in provisions.iteritems():
672 da9bcceb Giorgos Korfiatis
                ps = self.mk_provisions(holder, source, resource, quantity)
673 da9bcceb Giorgos Korfiatis
                request["provisions"].extend(ps)
674 12eab714 Ilias Tsitsimpis
        except Exception as err:
675 12eab714 Ilias Tsitsimpis
            self.logger.error(str(err))
676 12eab714 Ilias Tsitsimpis
            raise BadValue(str(err))
677 12eab714 Ilias Tsitsimpis
678 da9bcceb Giorgos Korfiatis
        return self._issue_commission(request)
679 12eab714 Ilias Tsitsimpis
680 1f4a46dd Giorgos Korfiatis
    def issue_resource_reassignment(self, holder, from_source,
681 1f4a46dd Giorgos Korfiatis
                                    to_source, provisions, name="",
682 1f4a46dd Giorgos Korfiatis
                                    force=False, auto_accept=False):
683 1f4a46dd Giorgos Korfiatis
        """Change resource assignment to another project
684 1f4a46dd Giorgos Korfiatis
        """
685 1f4a46dd Giorgos Korfiatis
686 1f4a46dd Giorgos Korfiatis
        request = {}
687 1f4a46dd Giorgos Korfiatis
        request["force"] = force
688 1f4a46dd Giorgos Korfiatis
        request["auto_accept"] = auto_accept
689 1f4a46dd Giorgos Korfiatis
        request["name"] = name
690 1f4a46dd Giorgos Korfiatis
691 1f4a46dd Giorgos Korfiatis
        try:
692 1f4a46dd Giorgos Korfiatis
            request["provisions"] = []
693 1f4a46dd Giorgos Korfiatis
            for resource, quantity in provisions.iteritems():
694 1f4a46dd Giorgos Korfiatis
                ps = self.mk_provisions(
695 1f4a46dd Giorgos Korfiatis
                    holder, from_source, resource, -quantity)
696 1f4a46dd Giorgos Korfiatis
                ps += self.mk_provisions(holder, to_source, resource, quantity)
697 1f4a46dd Giorgos Korfiatis
                request["provisions"].extend(ps)
698 1f4a46dd Giorgos Korfiatis
        except Exception as err:
699 1f4a46dd Giorgos Korfiatis
            self.logger.error(str(err))
700 1f4a46dd Giorgos Korfiatis
            raise BadValue(str(err))
701 1f4a46dd Giorgos Korfiatis
702 1f4a46dd Giorgos Korfiatis
        return self._issue_commission(request)
703 1f4a46dd Giorgos Korfiatis
704 7a0180ef Ilias Tsitsimpis
    # ----------------------------------
705 7b5a37fd Ilias Tsitsimpis
    # do a GET to ``API_COMMISSIONS``
706 2c9c147e Ilias Tsitsimpis
    def get_pending_commissions(self):
707 7a0180ef Ilias Tsitsimpis
        """Get Pending Commissions
708 7a0180ef Ilias Tsitsimpis

709 7a0180ef Ilias Tsitsimpis
        In case of success return a list of pending commissions' ids
710 7a0180ef Ilias Tsitsimpis
        (list of integers)
711 7a0180ef Ilias Tsitsimpis

712 7a0180ef Ilias Tsitsimpis
        """
713 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(self.api_commissions)
714 7a0180ef Ilias Tsitsimpis
715 994f37b6 Ilias Tsitsimpis
    # ----------------------------------
716 7b5a37fd Ilias Tsitsimpis
    # do a GET to ``API_COMMISSIONS``/<serial>
717 2c9c147e Ilias Tsitsimpis
    def get_commission_info(self, serial):
718 994f37b6 Ilias Tsitsimpis
        """Get Description of a Commission
719 994f37b6 Ilias Tsitsimpis

720 994f37b6 Ilias Tsitsimpis
        Keyword arguments:
721 994f37b6 Ilias Tsitsimpis
        serial  -- commission's id (int)
722 994f37b6 Ilias Tsitsimpis

723 994f37b6 Ilias Tsitsimpis
        In case of success return a dict of dicts containing
724 5c418e94 Ilias Tsitsimpis
        informations (details) about the requested commission
725 994f37b6 Ilias Tsitsimpis

726 994f37b6 Ilias Tsitsimpis
        """
727 10797183 Ilias Tsitsimpis
        check_input("get_commission_info", self.logger, serial=serial)
728 994f37b6 Ilias Tsitsimpis
729 2c9c147e Ilias Tsitsimpis
        path = self.api_commissions.rstrip('/') + "/" + str(serial)
730 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(path)
731 994f37b6 Ilias Tsitsimpis
732 805e294c Ilias Tsitsimpis
    # ----------------------------------
733 7b5a37fd Ilias Tsitsimpis
    # do a POST to ``API_COMMISSIONS``/<serial>/action"
734 2c9c147e Ilias Tsitsimpis
    def commission_action(self, serial, action):
735 b5008ef0 Ilias Tsitsimpis
        """Perform a commission action
736 805e294c Ilias Tsitsimpis

737 805e294c Ilias Tsitsimpis
        Keyword arguments:
738 805e294c Ilias Tsitsimpis
        serial  -- commission's id (int)
739 805e294c Ilias Tsitsimpis
        action  -- action to perform, currently accept/reject (string)
740 805e294c Ilias Tsitsimpis

741 805e294c Ilias Tsitsimpis
        In case of success return nothing.
742 805e294c Ilias Tsitsimpis

743 805e294c Ilias Tsitsimpis
        """
744 10797183 Ilias Tsitsimpis
        check_input("commission_action", self.logger,
745 10797183 Ilias Tsitsimpis
                    serial=serial, action=action)
746 805e294c Ilias Tsitsimpis
747 2c9c147e Ilias Tsitsimpis
        path = self.api_commissions.rstrip('/') + "/" + str(serial) + "/action"
748 805e294c Ilias Tsitsimpis
        req_headers = {'content-type': 'application/json'}
749 19198628 Ilias Tsitsimpis
        req_body = parse_request({str(action): ""}, self.logger)
750 2c9c147e Ilias Tsitsimpis
        self._call_astakos(path, headers=req_headers,
751 2c9c147e Ilias Tsitsimpis
                           body=req_body, method="POST")
752 805e294c Ilias Tsitsimpis
753 2c9c147e Ilias Tsitsimpis
    def accept_commission(self, serial):
754 b5008ef0 Ilias Tsitsimpis
        """Accept a commission (see commission_action)"""
755 2c9c147e Ilias Tsitsimpis
        self.commission_action(serial, "accept")
756 805e294c Ilias Tsitsimpis
757 2c9c147e Ilias Tsitsimpis
    def reject_commission(self, serial):
758 b5008ef0 Ilias Tsitsimpis
        """Reject a commission (see commission_action)"""
759 2c9c147e Ilias Tsitsimpis
        self.commission_action(serial, "reject")
760 805e294c Ilias Tsitsimpis
761 81875157 Ilias Tsitsimpis
    # ----------------------------------
762 7b5a37fd Ilias Tsitsimpis
    # do a POST to ``API_COMMISSIONS_ACTION``
763 2c9c147e Ilias Tsitsimpis
    def resolve_commissions(self, accept_serials, reject_serials):
764 81875157 Ilias Tsitsimpis
        """Resolve multiple commissions at once
765 81875157 Ilias Tsitsimpis

766 81875157 Ilias Tsitsimpis
        Keyword arguments:
767 81875157 Ilias Tsitsimpis
        accept_serials  -- commissions to accept (list of ints)
768 81875157 Ilias Tsitsimpis
        reject_serials  -- commissions to reject (list of ints)
769 81875157 Ilias Tsitsimpis

770 81875157 Ilias Tsitsimpis
        In case of success return a dict of dicts describing which
771 81875157 Ilias Tsitsimpis
        commissions accepted, which rejected and which failed to
772 81875157 Ilias Tsitsimpis
        resolved.
773 81875157 Ilias Tsitsimpis

774 81875157 Ilias Tsitsimpis
        """
775 d5f086f2 Ilias Tsitsimpis
        check_input("resolve_commissions", self.logger,
776 d5f086f2 Ilias Tsitsimpis
                    accept_serials=accept_serials,
777 d5f086f2 Ilias Tsitsimpis
                    reject_serials=reject_serials)
778 81875157 Ilias Tsitsimpis
779 81875157 Ilias Tsitsimpis
        req_headers = {'content-type': 'application/json'}
780 81875157 Ilias Tsitsimpis
        req_body = parse_request({"accept": accept_serials,
781 81875157 Ilias Tsitsimpis
                                  "reject": reject_serials},
782 81875157 Ilias Tsitsimpis
                                 self.logger)
783 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(self.api_commissions_action,
784 2c9c147e Ilias Tsitsimpis
                                  headers=req_headers, body=req_body,
785 2c9c147e Ilias Tsitsimpis
                                  method="POST")
786 81875157 Ilias Tsitsimpis
787 47bb45c0 Giorgos Korfiatis
    # ----------------------------
788 47bb45c0 Giorgos Korfiatis
    # do a GET to ``API_PROJECTS``
789 2c9c147e Ilias Tsitsimpis
    def get_projects(self, name=None, state=None, owner=None):
790 47bb45c0 Giorgos Korfiatis
        """Retrieve all accessible projects
791 47bb45c0 Giorgos Korfiatis

792 47bb45c0 Giorgos Korfiatis
        Arguments:
793 47bb45c0 Giorgos Korfiatis
        name  -- filter by name (optional)
794 47bb45c0 Giorgos Korfiatis
        state -- filter by state (optional)
795 47bb45c0 Giorgos Korfiatis
        owner -- filter by owner (optional)
796 47bb45c0 Giorgos Korfiatis

797 47bb45c0 Giorgos Korfiatis
        In case of success, return a list of project descriptions.
798 47bb45c0 Giorgos Korfiatis
        """
799 47bb45c0 Giorgos Korfiatis
        filters = {}
800 47bb45c0 Giorgos Korfiatis
        if name is not None:
801 47bb45c0 Giorgos Korfiatis
            filters["name"] = name
802 47bb45c0 Giorgos Korfiatis
        if state is not None:
803 47bb45c0 Giorgos Korfiatis
            filters["state"] = state
804 47bb45c0 Giorgos Korfiatis
        if owner is not None:
805 47bb45c0 Giorgos Korfiatis
            filters["owner"] = owner
806 47bb45c0 Giorgos Korfiatis
        req_headers = {'content-type': 'application/json'}
807 47bb45c0 Giorgos Korfiatis
        req_body = (parse_request({"filter": filters}, self.logger)
808 47bb45c0 Giorgos Korfiatis
                    if filters else None)
809 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(self.api_projects,
810 2c9c147e Ilias Tsitsimpis
                                  headers=req_headers, body=req_body)
811 47bb45c0 Giorgos Korfiatis
812 47bb45c0 Giorgos Korfiatis
    # -----------------------------------------
813 47bb45c0 Giorgos Korfiatis
    # do a GET to ``API_PROJECTS``/<project_id>
814 2c9c147e Ilias Tsitsimpis
    def get_project(self, project_id):
815 47bb45c0 Giorgos Korfiatis
        """Retrieve project description, if accessible
816 47bb45c0 Giorgos Korfiatis

817 47bb45c0 Giorgos Korfiatis
        Arguments:
818 47bb45c0 Giorgos Korfiatis
        project_id -- project identifier
819 47bb45c0 Giorgos Korfiatis

820 47bb45c0 Giorgos Korfiatis
        In case of success, return project description.
821 47bb45c0 Giorgos Korfiatis
        """
822 2c9c147e Ilias Tsitsimpis
        path = join_urls(self.api_projects, str(project_id))
823 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(path)
824 47bb45c0 Giorgos Korfiatis
825 47bb45c0 Giorgos Korfiatis
    # -----------------------------
826 47bb45c0 Giorgos Korfiatis
    # do a POST to ``API_PROJECTS``
827 2c9c147e Ilias Tsitsimpis
    def create_project(self, specs):
828 47bb45c0 Giorgos Korfiatis
        """Submit application to create a new project
829 47bb45c0 Giorgos Korfiatis

830 47bb45c0 Giorgos Korfiatis
        Arguments:
831 47bb45c0 Giorgos Korfiatis
        specs -- dict describing a project
832 47bb45c0 Giorgos Korfiatis

833 47bb45c0 Giorgos Korfiatis
        In case of success, return project and application identifiers.
834 47bb45c0 Giorgos Korfiatis
        """
835 47bb45c0 Giorgos Korfiatis
        req_headers = {'content-type': 'application/json'}
836 47bb45c0 Giorgos Korfiatis
        req_body = parse_request(specs, self.logger)
837 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(self.api_projects,
838 2c9c147e Ilias Tsitsimpis
                                  headers=req_headers, body=req_body,
839 2c9c147e Ilias Tsitsimpis
                                  method="POST")
840 47bb45c0 Giorgos Korfiatis
841 47bb45c0 Giorgos Korfiatis
    # ------------------------------------------
842 47bb45c0 Giorgos Korfiatis
    # do a POST to ``API_PROJECTS``/<project_id>
843 2c9c147e Ilias Tsitsimpis
    def modify_project(self, project_id, specs):
844 47bb45c0 Giorgos Korfiatis
        """Submit application to modify an existing project
845 47bb45c0 Giorgos Korfiatis

846 47bb45c0 Giorgos Korfiatis
        Arguments:
847 47bb45c0 Giorgos Korfiatis
        project_id -- project identifier
848 47bb45c0 Giorgos Korfiatis
        specs      -- dict describing a project
849 47bb45c0 Giorgos Korfiatis

850 47bb45c0 Giorgos Korfiatis
        In case of success, return project and application identifiers.
851 47bb45c0 Giorgos Korfiatis
        """
852 2c9c147e Ilias Tsitsimpis
        path = join_urls(self.api_projects, str(project_id))
853 47bb45c0 Giorgos Korfiatis
        req_headers = {'content-type': 'application/json'}
854 47bb45c0 Giorgos Korfiatis
        req_body = parse_request(specs, self.logger)
855 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(path, headers=req_headers,
856 2c9c147e Ilias Tsitsimpis
                                  body=req_body, method="POST")
857 47bb45c0 Giorgos Korfiatis
858 47bb45c0 Giorgos Korfiatis
    # -------------------------------------------------
859 47bb45c0 Giorgos Korfiatis
    # do a POST to ``API_PROJECTS``/<project_id>/action
860 2c9c147e Ilias Tsitsimpis
    def project_action(self, project_id, action, reason=""):
861 47bb45c0 Giorgos Korfiatis
        """Perform action on a project
862 47bb45c0 Giorgos Korfiatis

863 47bb45c0 Giorgos Korfiatis
        Arguments:
864 47bb45c0 Giorgos Korfiatis
        project_id -- project identifier
865 47bb45c0 Giorgos Korfiatis
        action     -- action to perform, one of "suspend", "unsuspend",
866 47bb45c0 Giorgos Korfiatis
                      "terminate", "reinstate"
867 47bb45c0 Giorgos Korfiatis
        reason     -- reason of performing the action
868 47bb45c0 Giorgos Korfiatis

869 47bb45c0 Giorgos Korfiatis
        In case of success, return nothing.
870 47bb45c0 Giorgos Korfiatis
        """
871 2c9c147e Ilias Tsitsimpis
        path = join_urls(self.api_projects, str(project_id))
872 47bb45c0 Giorgos Korfiatis
        path = join_urls(path, "action")
873 47bb45c0 Giorgos Korfiatis
        req_headers = {'content-type': 'application/json'}
874 47bb45c0 Giorgos Korfiatis
        req_body = parse_request({action: reason}, self.logger)
875 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(path, headers=req_headers,
876 2c9c147e Ilias Tsitsimpis
                                  body=req_body, method="POST")
877 47bb45c0 Giorgos Korfiatis
878 47bb45c0 Giorgos Korfiatis
    # --------------------------------
879 47bb45c0 Giorgos Korfiatis
    # do a GET to ``API_APPLICATIONS``
880 2c9c147e Ilias Tsitsimpis
    def get_applications(self, project=None):
881 47bb45c0 Giorgos Korfiatis
        """Retrieve all accessible applications
882 47bb45c0 Giorgos Korfiatis

883 47bb45c0 Giorgos Korfiatis
        Arguments:
884 47bb45c0 Giorgos Korfiatis
        project -- filter by project (optional)
885 47bb45c0 Giorgos Korfiatis

886 47bb45c0 Giorgos Korfiatis
        In case of success, return a list of application descriptions.
887 47bb45c0 Giorgos Korfiatis
        """
888 47bb45c0 Giorgos Korfiatis
        req_headers = {'content-type': 'application/json'}
889 47bb45c0 Giorgos Korfiatis
        body = {"project": project} if project is not None else None
890 47bb45c0 Giorgos Korfiatis
        req_body = parse_request(body, self.logger) if body else None
891 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(self.api_applications,
892 2c9c147e Ilias Tsitsimpis
                                  headers=req_headers, body=req_body)
893 47bb45c0 Giorgos Korfiatis
894 47bb45c0 Giorgos Korfiatis
    # -----------------------------------------
895 47bb45c0 Giorgos Korfiatis
    # do a GET to ``API_APPLICATIONS``/<app_id>
896 2c9c147e Ilias Tsitsimpis
    def get_application(self, app_id):
897 47bb45c0 Giorgos Korfiatis
        """Retrieve application description, if accessible
898 47bb45c0 Giorgos Korfiatis

899 47bb45c0 Giorgos Korfiatis
        Arguments:
900 47bb45c0 Giorgos Korfiatis
        app_id -- application identifier
901 47bb45c0 Giorgos Korfiatis

902 47bb45c0 Giorgos Korfiatis
        In case of success, return application description.
903 47bb45c0 Giorgos Korfiatis
        """
904 2c9c147e Ilias Tsitsimpis
        path = join_urls(self.api_applications, str(app_id))
905 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(path)
906 47bb45c0 Giorgos Korfiatis
907 47bb45c0 Giorgos Korfiatis
    # -------------------------------------------------
908 47bb45c0 Giorgos Korfiatis
    # do a POST to ``API_APPLICATIONS``/<app_id>/action
909 2c9c147e Ilias Tsitsimpis
    def application_action(self, app_id, action, reason=""):
910 47bb45c0 Giorgos Korfiatis
        """Perform action on an application
911 47bb45c0 Giorgos Korfiatis

912 47bb45c0 Giorgos Korfiatis
        Arguments:
913 47bb45c0 Giorgos Korfiatis
        app_id -- application identifier
914 47bb45c0 Giorgos Korfiatis
        action -- action to perform, one of "approve", "deny",
915 47bb45c0 Giorgos Korfiatis
                  "dismiss", "cancel"
916 47bb45c0 Giorgos Korfiatis
        reason -- reason of performing the action
917 47bb45c0 Giorgos Korfiatis

918 47bb45c0 Giorgos Korfiatis
        In case of success, return nothing.
919 47bb45c0 Giorgos Korfiatis
        """
920 2c9c147e Ilias Tsitsimpis
        path = join_urls(self.api_applications, str(app_id))
921 47bb45c0 Giorgos Korfiatis
        path = join_urls(path, "action")
922 47bb45c0 Giorgos Korfiatis
        req_headers = {'content-type': 'application/json'}
923 47bb45c0 Giorgos Korfiatis
        req_body = parse_request({action: reason}, self.logger)
924 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(path, headers=req_headers,
925 2c9c147e Ilias Tsitsimpis
                                  body=req_body, method="POST")
926 47bb45c0 Giorgos Korfiatis
927 47bb45c0 Giorgos Korfiatis
    # -------------------------------
928 47bb45c0 Giorgos Korfiatis
    # do a GET to ``API_MEMBERSHIPS``
929 2c9c147e Ilias Tsitsimpis
    def get_memberships(self, project=None):
930 47bb45c0 Giorgos Korfiatis
        """Retrieve all accessible memberships
931 47bb45c0 Giorgos Korfiatis

932 47bb45c0 Giorgos Korfiatis
        Arguments:
933 47bb45c0 Giorgos Korfiatis
        project -- filter by project (optional)
934 47bb45c0 Giorgos Korfiatis

935 47bb45c0 Giorgos Korfiatis
        In case of success, return a list of membership descriptions.
936 47bb45c0 Giorgos Korfiatis
        """
937 47bb45c0 Giorgos Korfiatis
        req_headers = {'content-type': 'application/json'}
938 47bb45c0 Giorgos Korfiatis
        body = {"project": project} if project is not None else None
939 47bb45c0 Giorgos Korfiatis
        req_body = parse_request(body, self.logger) if body else None
940 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(self.api_memberships,
941 2c9c147e Ilias Tsitsimpis
                                  headers=req_headers, body=req_body)
942 47bb45c0 Giorgos Korfiatis
943 47bb45c0 Giorgos Korfiatis
    # -----------------------------------------
944 47bb45c0 Giorgos Korfiatis
    # do a GET to ``API_MEMBERSHIPS``/<memb_id>
945 2c9c147e Ilias Tsitsimpis
    def get_membership(self, memb_id):
946 47bb45c0 Giorgos Korfiatis
        """Retrieve membership description, if accessible
947 47bb45c0 Giorgos Korfiatis

948 47bb45c0 Giorgos Korfiatis
        Arguments:
949 47bb45c0 Giorgos Korfiatis
        memb_id -- membership identifier
950 47bb45c0 Giorgos Korfiatis

951 47bb45c0 Giorgos Korfiatis
        In case of success, return membership description.
952 47bb45c0 Giorgos Korfiatis
        """
953 2c9c147e Ilias Tsitsimpis
        path = join_urls(self.api_memberships, str(memb_id))
954 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(path)
955 47bb45c0 Giorgos Korfiatis
956 47bb45c0 Giorgos Korfiatis
    # -------------------------------------------------
957 47bb45c0 Giorgos Korfiatis
    # do a POST to ``API_MEMBERSHIPS``/<memb_id>/action
958 2c9c147e Ilias Tsitsimpis
    def membership_action(self, memb_id, action, reason=""):
959 47bb45c0 Giorgos Korfiatis
        """Perform action on a membership
960 47bb45c0 Giorgos Korfiatis

961 47bb45c0 Giorgos Korfiatis
        Arguments:
962 47bb45c0 Giorgos Korfiatis
        memb_id -- membership identifier
963 47bb45c0 Giorgos Korfiatis
        action  -- action to perform, one of "leave", "cancel", "accept",
964 47bb45c0 Giorgos Korfiatis
                   "reject", "remove"
965 47bb45c0 Giorgos Korfiatis
        reason  -- reason of performing the action
966 47bb45c0 Giorgos Korfiatis

967 47bb45c0 Giorgos Korfiatis
        In case of success, return nothing.
968 47bb45c0 Giorgos Korfiatis
        """
969 2c9c147e Ilias Tsitsimpis
        path = join_urls(self.api_memberships, str(memb_id))
970 47bb45c0 Giorgos Korfiatis
        path = join_urls(path, "action")
971 47bb45c0 Giorgos Korfiatis
        req_headers = {'content-type': 'application/json'}
972 47bb45c0 Giorgos Korfiatis
        req_body = parse_request({action: reason}, self.logger)
973 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(path, headers=req_headers,
974 2c9c147e Ilias Tsitsimpis
                                  body=req_body, method="POST")
975 47bb45c0 Giorgos Korfiatis
976 47bb45c0 Giorgos Korfiatis
    # --------------------------------
977 47bb45c0 Giorgos Korfiatis
    # do a POST to ``API_MEMBERSHIPS``
978 2c9c147e Ilias Tsitsimpis
    def join_project(self, project_id):
979 47bb45c0 Giorgos Korfiatis
        """Join a project
980 47bb45c0 Giorgos Korfiatis

981 47bb45c0 Giorgos Korfiatis
        Arguments:
982 47bb45c0 Giorgos Korfiatis
        project_id -- project identifier
983 47bb45c0 Giorgos Korfiatis

984 47bb45c0 Giorgos Korfiatis
        In case of success, return membership identifier.
985 47bb45c0 Giorgos Korfiatis
        """
986 47bb45c0 Giorgos Korfiatis
        req_headers = {'content-type': 'application/json'}
987 47bb45c0 Giorgos Korfiatis
        body = {"join": {"project": project_id}}
988 47bb45c0 Giorgos Korfiatis
        req_body = parse_request(body, self.logger)
989 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(self.api_memberships, headers=req_headers,
990 2c9c147e Ilias Tsitsimpis
                                  body=req_body, method="POST")
991 47bb45c0 Giorgos Korfiatis
992 47bb45c0 Giorgos Korfiatis
    # --------------------------------
993 47bb45c0 Giorgos Korfiatis
    # do a POST to ``API_MEMBERSHIPS``
994 2c9c147e Ilias Tsitsimpis
    def enroll_member(self, project_id, email):
995 47bb45c0 Giorgos Korfiatis
        """Enroll a user in a project
996 47bb45c0 Giorgos Korfiatis

997 47bb45c0 Giorgos Korfiatis
        Arguments:
998 47bb45c0 Giorgos Korfiatis
        project_id -- project identifier
999 47bb45c0 Giorgos Korfiatis
        email      -- user identified by email
1000 47bb45c0 Giorgos Korfiatis

1001 47bb45c0 Giorgos Korfiatis
        In case of success, return membership identifier.
1002 47bb45c0 Giorgos Korfiatis
        """
1003 47bb45c0 Giorgos Korfiatis
        req_headers = {'content-type': 'application/json'}
1004 47bb45c0 Giorgos Korfiatis
        body = {"enroll": {"project": project_id, "user": email}}
1005 47bb45c0 Giorgos Korfiatis
        req_body = parse_request(body, self.logger)
1006 2c9c147e Ilias Tsitsimpis
        return self._call_astakos(self.api_memberships, headers=req_headers,
1007 2c9c147e Ilias Tsitsimpis
                                  body=req_body, method="POST")
1008 2c9c147e Ilias Tsitsimpis
1009 d2104099 Sofia Papagiannaki
    # --------------------------------
1010 fe7d0186 Sofia Papagiannaki
    # do a POST to ``API_OAUTH2_TOKEN``
1011 d2104099 Sofia Papagiannaki
    def get_token(self, grant_type, client_id, client_secret, **body_params):
1012 75144caa Sofia Papagiannaki
        headers = {'content-type': 'application/x-www-form-urlencoded',
1013 d2104099 Sofia Papagiannaki
                   'Authorization': 'Basic %s' % b64encode('%s:%s' %
1014 d2104099 Sofia Papagiannaki
                                                           (client_id,
1015 d2104099 Sofia Papagiannaki
                                                            client_secret))}
1016 d2104099 Sofia Papagiannaki
        body_params['grant_type'] = grant_type
1017 d2104099 Sofia Papagiannaki
        body = urllib.urlencode(body_params)
1018 fe7d0186 Sofia Papagiannaki
        return self._call_astakos(self.api_oauth2_token, headers=headers,
1019 d2104099 Sofia Papagiannaki
                                  body=body, method="POST")
1020 d2104099 Sofia Papagiannaki
1021 2c9c147e Ilias Tsitsimpis
1022 2c9c147e Ilias Tsitsimpis
# --------------------------------------------------------------------
1023 2c9c147e Ilias Tsitsimpis
# parse endpoints
1024 2c9c147e Ilias Tsitsimpis
def parse_endpoints(endpoints, ep_name=None, ep_type=None,
1025 2c9c147e Ilias Tsitsimpis
                    ep_region=None, ep_version_id=None):
1026 2c9c147e Ilias Tsitsimpis
    """Parse endpoints server response and extract the ones needed
1027 2c9c147e Ilias Tsitsimpis

1028 2c9c147e Ilias Tsitsimpis
    Keyword arguments:
1029 2c9c147e Ilias Tsitsimpis
    endpoints     -- the endpoints (json response from get_endpoints)
1030 2c9c147e Ilias Tsitsimpis
    ep_name       -- return only endpoints with this name (optional)
1031 2c9c147e Ilias Tsitsimpis
    ep_type       -- return only endpoints with this type (optional)
1032 2c9c147e Ilias Tsitsimpis
    ep_region     -- return only endpoints with this region (optional)
1033 2c9c147e Ilias Tsitsimpis
    ep_version_id -- return only endpoints with this versionId (optional)
1034 2c9c147e Ilias Tsitsimpis

1035 9c3cbd0d Ilias Tsitsimpis
    In case one of the `name', `type', `region', `version_id' parameters
1036 2c9c147e Ilias Tsitsimpis
    is given, return only the endpoints that match all of these criteria.
1037 2c9c147e Ilias Tsitsimpis
    If no match is found then raise NoEndpoints exception.
1038 2c9c147e Ilias Tsitsimpis

1039 2c9c147e Ilias Tsitsimpis
    """
1040 2c9c147e Ilias Tsitsimpis
    try:
1041 2c9c147e Ilias Tsitsimpis
        catalog = endpoints['access']['serviceCatalog']
1042 2c9c147e Ilias Tsitsimpis
        if ep_name is not None:
1043 2c9c147e Ilias Tsitsimpis
            catalog = \
1044 2c9c147e Ilias Tsitsimpis
                [c for c in catalog if c['name'] == ep_name]
1045 2c9c147e Ilias Tsitsimpis
        if ep_type is not None:
1046 2c9c147e Ilias Tsitsimpis
            catalog = \
1047 2c9c147e Ilias Tsitsimpis
                [c for c in catalog if c['type'] == ep_type]
1048 2c9c147e Ilias Tsitsimpis
        if ep_region is not None:
1049 2c9c147e Ilias Tsitsimpis
            for c in catalog:
1050 2c9c147e Ilias Tsitsimpis
                c['endpoints'] = [e for e in c['endpoints']
1051 2c9c147e Ilias Tsitsimpis
                                  if e['region'] == ep_region]
1052 2c9c147e Ilias Tsitsimpis
            # Remove catalog entries with no endpoints
1053 2c9c147e Ilias Tsitsimpis
            catalog = \
1054 2c9c147e Ilias Tsitsimpis
                [c for c in catalog if c['endpoints']]
1055 2c9c147e Ilias Tsitsimpis
        if ep_version_id is not None:
1056 2c9c147e Ilias Tsitsimpis
            for c in catalog:
1057 2c9c147e Ilias Tsitsimpis
                c['endpoints'] = [e for e in c['endpoints']
1058 2c9c147e Ilias Tsitsimpis
                                  if e['versionId'] == ep_version_id]
1059 2c9c147e Ilias Tsitsimpis
            # Remove catalog entries with no endpoints
1060 2c9c147e Ilias Tsitsimpis
            catalog = \
1061 2c9c147e Ilias Tsitsimpis
                [c for c in catalog if c['endpoints']]
1062 2c9c147e Ilias Tsitsimpis
1063 2c9c147e Ilias Tsitsimpis
        if not catalog:
1064 2c9c147e Ilias Tsitsimpis
            raise NoEndpoints(ep_name, ep_type,
1065 2c9c147e Ilias Tsitsimpis
                              ep_region, ep_version_id)
1066 2c9c147e Ilias Tsitsimpis
        else:
1067 2c9c147e Ilias Tsitsimpis
            return catalog
1068 2c9c147e Ilias Tsitsimpis
    except KeyError:
1069 259a5f9a Ilias Tsitsimpis
        raise NoEndpoints(ep_name, ep_type, ep_region, ep_version_id)
1070 2c9c147e Ilias Tsitsimpis
1071 f54cf5e4 Ilias Tsitsimpis
1072 f54cf5e4 Ilias Tsitsimpis
# --------------------------------------------------------------------
1073 cbc0b438 Ilias Tsitsimpis
# Private functions
1074 45c0bcf8 Giorgos Korfiatis
# We want _do_request to be a distinct function
1075 f93cc364 Ilias Tsitsimpis
# so that we can replace it during unit tests.
1076 794c94e6 Ilias Tsitsimpis
def _do_request(conn, method, url, **kwargs):
1077 f8388a90 Ilias Tsitsimpis
    """The actual request. This function can easily be mocked"""
1078 f8388a90 Ilias Tsitsimpis
    conn.request(method, url, **kwargs)
1079 f8388a90 Ilias Tsitsimpis
    response = conn.getresponse()
1080 f8388a90 Ilias Tsitsimpis
    length = response.getheader('content-length', None)
1081 f8388a90 Ilias Tsitsimpis
    data = response.read(length)
1082 f8388a90 Ilias Tsitsimpis
    status = int(response.status)
1083 21190887 Ilias Tsitsimpis
    message = response.reason
1084 21190887 Ilias Tsitsimpis
    return (message, data, status)