Dont call astakos form kamaki, use astakosclient
[kamaki] / kamaki / clients / astakos / __init__.py
1 # Copyright 2012-2013 GRNET S.A. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6 #
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10 #
11 #   2. Redistributions in binary form must reproduce the above
12 #      copyright notice, this list of conditions and the following
13 #      disclaimer in the documentation and/or other materials
14 #      provided with the distribution.
15 #
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
28 #
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
33
34 from logging import getLogger
35 from astakosclient import AstakosClient as SynnefoAstakosClient
36
37 from kamaki.clients import Client, ClientError
38
39
40 class AstakosClient(Client):
41     """Synnefo Astakos API client"""
42
43     def __init__(self, base_url, token=None):
44         super(AstakosClient, self).__init__(base_url, token)
45         self._cache = {}
46         self._uuids = {}
47         self.log = getLogger(__name__)
48         self.astakos = dict()
49
50     def _resolve_token(foo):
51         def wrap(self, *args, **kwargs):
52             args = list(args)
53             token = kwargs.get('token', args.pop())
54             if token and token not in self.astakos:
55                 self.astakos[token] = SynnefoAstakosClient(
56                     self.token, self.base_url,
57                     logger=getLogger('astakosclient'))
58             else:
59                 token = self.token
60             kwargs['token'] = token
61             return foo(self, *args, **kwargs)
62         return wrap
63
64     @_resolve_token
65     def authenticate(self, token=None):
66         """Get authentication information and store it in this client
67         As long as the AstakosClient instance is alive, the latest
68         authentication information for this token will be available
69
70         :param token: (str) custom token to authenticate
71
72         :returns: (dict) authentication information
73         """
74         r = self.astakos[token].get_endpoints()
75         #  body = dict(auth=dict(token=dict(id=self.token)))
76         #  r = self.post('/tokens', json=body).json
77         uuid = r['access']['user']['id']
78         self._uuids[token] = uuid
79         self._cache[uuid] = r
80         return self._cache[uuid]
81
82     def get_token(self, uuid):
83         return self._cache[uuid]['access']['token']['id']
84
85     @_resolve_token
86     def get_services(self, token=None):
87         """
88         :returns: (list) [{name:..., type:..., endpoints:[...]}, ...]
89         """
90         try:
91             r = self._cache[self._uuids[token]]
92         except KeyError:
93             r = self.authenticate(token)
94         return r['access']['serviceCatalog']
95
96     @_resolve_token
97     def get_service_details(self, service_type, token=None):
98         """
99         :param service_type: (str) compute, object-store, image, account, etc.
100
101         :returns: (dict) {name:..., type:..., endpoints:[...]}
102
103         :raises ClientError: (600) if service_type not in service catalog
104         """
105         services = self.get_services(token)
106         for service in services:
107             try:
108                 if service['type'].lower() == service_type.lower():
109                     return service
110             except KeyError:
111                 self.log.warning('Misformated service %s' % service)
112         raise ClientError(
113             'Service type "%s" not in service catalog' % service_type, 600)
114
115     @_resolve_token
116     def get_service_endpoints(self, service_type, version=None, token=None):
117         """
118         :param service_type: (str) can be compute, object-store, etc.
119
120         :param version: (str) the version id of the service
121
122         :returns: (dict) {SNF:uiURL, adminURL, internalURL, publicURL, ...}
123
124         :raises ClientError: (600) if service_type not in service catalog
125
126         :raises ClientError: (601) if #matching endpoints != 1
127         """
128         service = self.get_service_details(service_type, token)
129         matches = []
130         for endpoint in service['endpoints']:
131             if (not version) or (
132                     endpoint['versionId'].lower() == version.lower()):
133                 matches.append(endpoint)
134         if len(matches) != 1:
135             raise ClientError(
136                 '%s endpoints match type %s %s' % (
137                     len(matches), service_type,
138                     ('and versionId %s' % version) if version else ''),
139                 601)
140         return matches[0]
141
142     @_resolve_token
143     def list_users(self):
144         """list cached users information"""
145         if not self._cache:
146             self.authenticate()
147         r = []
148         for k, v in self._cache.items():
149             r.append(dict(v['access']['user']))
150             r[-1].update(dict(auth_token=self.get_token(k)))
151         return r
152
153     @_resolve_token
154     def user_info(self, token=None):
155         """Get (cached) user information"""
156         token_bu = self.token or token
157         token = token or self.token
158         try:
159             r = self._cache[self._uuids[token]]
160         except KeyError:
161             r = self.authenticate(token)
162         finally:
163             self.token = token_bu
164         return r['access']['user']
165
166     @_resolve_token
167     def term(self, key, token=None):
168         """Get (cached) term, from user credentials"""
169         return self.user_term(key, token)
170
171     @_resolve_token
172     def user_term(self, key, token=None):
173         """Get (cached) term, from user credentials"""
174         return self.user_info(token).get(key, None)
175
176     def post_user_catalogs(self, uuids=None, displaynames=None):
177         """POST base_url/user_catalogs
178
179         :param uuids: (list or tuple) user uuids
180
181         :param displaynames: (list or tuple) usernames (mut. excl. to uuids)
182
183         :returns: (dict) {uuid1: name1, uuid2: name2, ...} or oposite
184         """
185         if uuids:
186             return self.astakos[self.token].get_usernames(uuids)
187         else:
188             return self.astakos[self.token].get_uuids(displaynames)
189         #json_data = dict(uuids=uuids) if (
190         #    uuids) else dict(displaynames=displaynames)
191         #return account.post('user_catalogs', json=json_data)