Fully adjust cyclades_cli
[kamaki] / kamaki / cli / commands / errors.py
1 # Copyright 2011-2012 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.command
33
34 from traceback import print_stack, print_exc
35 import logging
36
37 from kamaki.clients import ClientError
38 from kamaki.cli.errors import CLIError, raiseCLIError, CLISyntaxError
39 from kamaki.cli import _debug, kloger
40
41 sendlog = logging.getLogger('clients.send')
42 datasendlog = logging.getLogger('data.send')
43 recvlog = logging.getLogger('clients.recv')
44 datarecvlog = logging.getLogger('data.recv')
45
46
47 class generic(object):
48
49     @classmethod
50     def all(this, foo):
51         def _raise(self, *args, **kwargs):
52             try:
53                 return foo(self, *args, **kwargs)
54             except Exception as e:
55                 if _debug:
56                     print_stack()
57                     print_exc(e)
58                 raiseCLIError(e)
59         return _raise
60
61     @classmethod
62     def _connection(this, foo, base_url):
63         def _raise(self, *args, **kwargs):
64             try:
65                 foo(self, *args, **kwargs)
66             except ClientError as ce:
67                 if ce.status == 401:
68                     raiseCLIError(ce, 'Authorization failed', details=[
69                         'Make sure a valid token is provided:',
70                         '  to check if token is valid: /astakos authenticate',
71                         '  to set token: /config set [.server.]token <token>',
72                         '  to get current token: /config get [server.]token'])
73                 elif ce.status in range(-12, 200) + [403, 302, 500]:
74                     raiseCLIError(ce, importance=3, details=[
75                         'Check if service is up or set to url %s' % base_url,
76                         '  to get url: /config get %s' % base_url,
77                         '  to set url: /config set %s <URL>' % base_url])
78                 elif ce.status == 404\
79                 and 'kamakihttpresponse' in ('%s' % ce).lower():
80                     client = getattr(self, 'client', None)
81                     if not client:
82                         raise
83                     url = getattr(client, 'base_url', '<empty>')
84                     raiseCLIError(ce,
85                         'Invalid service url %s' % url,
86                         details=[
87                         'Please, check if service url is correctly set',
88                         '* to get current url: /config get compute.url',
89                         '* to set url: /config set compute.url <URL>'])
90                 raise
91         return _raise
92
93
94 class astakos(object):
95
96     _token_details = [
97         'To check default token: /config get token',
98         'If set/update a token:',
99         '*  (permanent):    /config set token <token>',
100         '*  (temporary):    re-run with <token> parameter']
101
102     @classmethod
103     def load(this, foo):
104         def _raise(self, *args, **kwargs):
105             r = foo(self, *args, **kwargs)
106             try:
107                 client = getattr(self, 'client')
108             except AttributeError as ae:
109                 raiseCLIError(ae, 'Client setup failure', importance=3)
110             if not getattr(client, 'token', False):
111                 kloger.warning(
112                     'No permanent token (try: kamaki config set token <tkn>)')
113             if not getattr(client, 'base_url', False):
114                 raise CLIError('Missing astakos server URL',
115                     importance=3,
116                     details=['Check if astakos.url is set correctly',
117                     'To get astakos url:   /config get astakos.url',
118                     'To set astakos url:   /config set astakos.url <URL>'])
119             return r
120         return _raise
121
122     @classmethod
123     def authenticate(this, foo):
124         def _raise(self, *args, **kwargs):
125             try:
126                 r = foo(self, *args, **kwargs)
127             except ClientError as ce:
128                 if ce.status == 401:
129                     token = kwargs.get('custom_token', 0) or self.client.token
130                     raiseCLIError(ce,
131                         'Authorization failed for token %s' % token if token\
132                             else 'No token provided',
133                         details=[] if token else this._token_details)
134             self._raise = foo
135             return r
136         return _raise
137
138
139 class history(object):
140     @classmethod
141     def init(this, foo):
142         def _raise(self, *args, **kwargs):
143             r = foo(self, *args, **kwargs)
144             if not hasattr(self, 'history'):
145                 raise CLIError('Failed to load history', importance=2)
146             return r
147         return _raise
148
149     @classmethod
150     def _get_cmd_ids(this, foo):
151         def _raise(self, cmd_ids, *args, **kwargs):
152             if not cmd_ids:
153                 raise CLISyntaxError('Usage: <id1|id1-id2> [id3|id3-id4] ...',
154                     details=self.__doc__.split('\n'))
155             return foo(self, cmd_ids, *args, **kwargs)
156         return _raise
157
158
159 class cyclades(object):
160     about_flavor_id = [
161         'How to pick a valid flavor id:',
162         '* get a list of flavor ids: /flavor list',
163         '* details of flavor: /flavor info <flavor id>']
164
165     about_network_id = [
166         'How to pick a valid network id:',
167         '* get a list of network ids: /network list',
168         '* details of network: /network info <network id>']
169
170     @classmethod
171     def connection(this, foo):
172         return generic._connection(foo, 'compute.url')
173
174     @classmethod
175     def date(this, foo):
176         def _raise(self, *args, **kwargs):
177             try:
178                 return foo(self, *args, **kwargs)
179             except ClientError as ce:
180                 if ce.status == 400 and 'changes-since' in ('%s' % ce):
181                     raise CLIError(
182                         'Incorrect date format for --since',
183                         details=['Accepted date format: d/m/y'])
184                 raise
185         return _raise
186
187     @classmethod
188     def network_id(this, foo):
189         def _raise(self, *args, **kwargs):
190             network_id = kwargs.get('network_id', None)
191             try:
192                 network_id = int(network_id)
193                 return foo(self, *args, **kwargs)
194             except ValueError as ve:
195                 raiseCLIError(ve, 'Invalid network id %s ' % network_id,
196                     details='network id must be a positive integer',
197                     importance=1)
198             except ClientError as ce:
199                 if network_id and ce.status == 404 and\
200                     'network' in ('%s' % ce).lower():
201                         raiseCLIError(ce,
202                             'No network with id %s found' % network_id,
203                             details=this.about_network_id)
204                 raise
205         return _raise
206
207     @classmethod
208     def network_max(this, foo):
209         def _raise(self, *args, **kwargs):
210             try:
211                 return foo(self, *args, **kwargs)
212             except ClientError as ce:
213                 if ce.status == 413:
214                     raiseCLIError(ce,
215                         'Cannot create another network',
216                         details=['Maximum number of networks reached',
217                             '* to get a list of networks: /network list',
218                             '* to delete a network: /network delete <net id>'])
219                 raise
220         return _raise
221
222     @classmethod
223     def network_in_use(this, foo):
224         def _raise(self, *args, **kwargs):
225             network_id = kwargs.get('network_id', None)
226             try:
227                 return foo(self, *args, **kwargs)
228             except ClientError as ce:
229                 if network_id or ce.status == 421:
230                     raiseCLIError(ce,
231                         'Network with id %s is in use' % network_id,
232                         details=[
233                             'Disconnect all nics/VMs of this network first',
234                             '* to get nics: /network info %s' % network_id,
235                             '.  (under "attachments" section)',
236                             '* to disconnect: /network disconnect <nic id>'])
237                 raise
238         return _raise
239
240     @classmethod
241     def flavor_id(this, foo):
242         def _raise(self, *args, **kwargs):
243             flavor_id = kwargs.get('flavor_id', None)
244             try:
245                 flavor_id = int(flavor_id)
246                 return foo(self, *args, **kwargs)
247             except ValueError as ve:
248                 raiseCLIError(ve, 'Invalid flavor id %s ' % flavor_id,
249                     details='Flavor id must be a positive integer',
250                     importance=1)
251             except ClientError as ce:
252                 if flavor_id and ce.status == 404 and\
253                     'flavor' in ('%s' % ce).lower():
254                         raiseCLIError(ce,
255                             'No flavor with id %s found' % flavor_id,
256                             details=this.about_flavor_id)
257                 raise
258         return _raise
259
260     @classmethod
261     def server_id(this, foo):
262         def _raise(self, *args, **kwargs):
263             server_id = kwargs.get('server_id', None)
264             try:
265                 server_id = int(server_id)
266                 return foo(self, *args, **kwargs)
267             except ValueError as ve:
268                 raiseCLIError(ve,
269                     'Invalid server(VM) id %s' % server_id,
270                     details=['id must be a positive integer'],
271                     importance=1)
272             except ClientError as ce:
273                 err_msg = ('%s' % ce).lower()
274                 if (ce.status == 404 and 'server' in err_msg)\
275                 or (ce.status == 400 and 'not found' in err_msg):
276                     raiseCLIError(ce,
277                         'server(VM) with id %s not found' % server_id,
278                         details=[
279                             '* to get existing VM ids: /server list',
280                             '* to get VM details: /server info <VM id>'])
281                 raise
282         return _raise
283
284     @classmethod
285     def firewall(this, foo):
286         def _raise(self, *args, **kwargs):
287             profile = kwargs.get('profile', None)
288             try:
289                 return foo(self, *args, **kwargs)
290             except ClientError as ce:
291                 if ce.status == 400 and profile\
292                 and 'firewall' in ('%s' % ce).lower():
293                     raiseCLIError(ce,
294                         '%s is an invalid firewall profile term' % profile,
295                         details=['Try one of the following:',
296                             '* DISABLED: Shutdown firewall',
297                             '* ENABLED: Firewall in normal mode',
298                             '* PROTECTED: Firewall in secure mode'])
299                 raise
300         return _raise
301
302     @classmethod
303     def nic_id(this, foo):
304         def _raise(self, *args, **kwargs):
305             try:
306                 return foo(self, *args, **kwargs)
307             except ClientError as ce:
308                 nic_id = kwargs.get('nic_id', None)
309                 if nic_id and ce.status == 404\
310                 and 'network interface' in ('%s' % ce).lower():
311                     server_id = kwargs.get('server_id', '<no server>')
312                     err_msg = 'No nic %s on server(VM) with id %s' % (
313                         nic_id,
314                         server_id)
315                     raiseCLIError(ce, err_msg, details=[
316                         '* check server(VM) with id %s: /server info %s' % (
317                             server_id,
318                             server_id),
319                         '* list nics for server(VM) with id %s:' % server_id,
320                         '      /server addr %s' % server_id])
321                 raise
322         return _raise
323
324     @classmethod
325     def nic_format(this, foo):
326         def _raise(self, *args, **kwargs):
327             try:
328                 return foo(self, *args, **kwargs)
329             except IndexError as ie:
330                 nic_id = kwargs.get('nic_id', None)
331                 raiseCLIError(ie,
332                     'Invalid format for network interface (nic) %s' % nic_id,
333                     importance=1,
334                     details=[
335                         'nid_id format: nic-<server id>-<nic id>',
336                         '* get nics of a network: /network info <net id>',
337                         '    (listed the "attachments" section)'])
338         return _raise
339
340     @classmethod
341     def metadata(this, foo):
342         def _raise(self, *args, **kwargs):
343             key = kwargs.get('key', None)
344             try:
345                 foo(self, *args, **kwargs)
346             except ClientError as ce:
347                 if key and ce.status == 404\
348                     and 'metadata' in ('%s' % ce).lower():
349                         raiseCLIError(ce, 'No VM metadata with key %s' % key)
350                 raise
351         return _raise
352
353
354 class plankton(object):
355
356     about_image_id = ['How to pick a suitable image:',
357         '* get a list of image ids: /image list',
358         '* details of image: /flavor info <image id>']
359
360     @classmethod
361     def connection(this, foo):
362         return generic._connection(foo, 'image.url')
363
364     @classmethod
365     def id(this, foo):
366         def _raise(self, *args, **kwargs):
367             image_id = kwargs.get('image_id', None)
368             try:
369                 foo(self, *args, **kwargs)
370             except ClientError as ce:
371                 if image_id and (ce.status == 404\
372                     or (ce.status == 400 and
373                         'image not found' in ('%s' % ce).lower())\
374                     or ce.status == 411):
375                         raiseCLIError(ce,
376                             'No image with id %s found' % image_id,
377                             details=this.about_image_id)
378                 raise
379         return _raise
380
381     @classmethod
382     def metadata(this, foo):
383         def _raise(self, *args, **kwargs):
384             key = kwargs.get('key', None)
385             try:
386                 foo(self, *args, **kwargs)
387             except ClientError as ce:
388                 if ce.status == 404 or ((ce.status == 400\
389                     and 'metadata' in ('%s' % ce).lower())):
390                         raiseCLIError(ce,
391                             'No properties with key %s in this image' % key)
392                 raise
393         return _raise