Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / errors.py @ ca092af4

History | View | Annotate | Download (19.9 kB)

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
from kamaki.cli.utils import format_size
41

    
42
sendlog = logging.getLogger('clients.send')
43
datasendlog = logging.getLogger('data.send')
44
recvlog = logging.getLogger('clients.recv')
45
datarecvlog = logging.getLogger('data.recv')
46

    
47

    
48
class generic(object):
49

    
50
    @classmethod
51
    def all(this, foo):
52
        def _raise(self, *args, **kwargs):
53
            try:
54
                return foo(self, *args, **kwargs)
55
            except Exception as e:
56
                if _debug:
57
                    print_stack()
58
                    print_exc(e)
59
                raiseCLIError(e)
60
        return _raise
61

    
62
    @classmethod
63
    def _connection(this, foo, base_url):
64
        def _raise(self, *args, **kwargs):
65
            try:
66
                foo(self, *args, **kwargs)
67
            except ClientError as ce:
68
                if ce.status == 401:
69
                    raiseCLIError(ce, 'Authorization failed', details=[
70
                        'Make sure a valid token is provided:',
71
                        '  to check if token is valid: /astakos authenticate',
72
                        '  to set token: /config set [.server.]token <token>',
73
                        '  to get current token: /config get [server.]token'])
74
                elif ce.status in range(-12, 200) + [302, 401, 403, 500]:
75
                    raiseCLIError(ce, importance=3, details=[
76
                        'Check if service is up or set to url %s' % base_url,
77
                        '  to get url: /config get %s' % base_url,
78
                        '  to set url: /config set %s <URL>' % base_url])
79
                elif ce.status == 404\
80
                and 'kamakihttpresponse' in ('%s' % ce).lower():
81
                    client = getattr(self, 'client', None)
82
                    if not client:
83
                        raise
84
                    url = getattr(client, 'base_url', '<empty>')
85
                    raiseCLIError(ce,
86
                        'Invalid service url %s' % url,
87
                        details=[
88
                        'Please, check if service url is correctly set',
89
                        '* to get current url: /config get compute.url',
90
                        '* to set url: /config set compute.url <URL>'])
91
                raise
92
        return _raise
93

    
94

    
95
class astakos(object):
96

    
97
    _token_details = [
98
        'To check default token: /config get token',
99
        'If set/update a token:',
100
        '*  (permanent):    /config set token <token>',
101
        '*  (temporary):    re-run with <token> parameter']
102

    
103
    @classmethod
104
    def load(this, foo):
105
        def _raise(self, *args, **kwargs):
106
            r = foo(self, *args, **kwargs)
107
            try:
108
                client = getattr(self, 'client')
109
            except AttributeError as ae:
110
                raiseCLIError(ae, 'Client setup failure', importance=3)
111
            if not getattr(client, 'token', False):
112
                kloger.warning(
113
                    'No permanent token (try: kamaki config set token <tkn>)')
114
            if not getattr(client, 'base_url', False):
115
                raise CLIError('Missing astakos server URL',
116
                    importance=3,
117
                    details=['Check if astakos.url is set correctly',
118
                    'To get astakos url:   /config get astakos.url',
119
                    'To set astakos url:   /config set astakos.url <URL>'])
120
            return r
121
        return _raise
122

    
123
    @classmethod
124
    def authenticate(this, foo):
125
        def _raise(self, *args, **kwargs):
126
            try:
127
                r = foo(self, *args, **kwargs)
128
            except ClientError as ce:
129
                if ce.status == 401:
130
                    token = kwargs.get('custom_token', 0) or self.client.token
131
                    raiseCLIError(ce,
132
                        'Authorization failed for token %s' % token if token\
133
                            else 'No token provided',
134
                        details=[] if token else this._token_details)
135
            self._raise = foo
136
            return r
137
        return _raise
138

    
139

    
140
class history(object):
141
    @classmethod
142
    def init(this, foo):
143
        def _raise(self, *args, **kwargs):
144
            r = foo(self, *args, **kwargs)
145
            if not hasattr(self, 'history'):
146
                raise CLIError('Failed to load history', importance=2)
147
            return r
148
        return _raise
149

    
150
    @classmethod
151
    def _get_cmd_ids(this, foo):
152
        def _raise(self, cmd_ids, *args, **kwargs):
153
            if not cmd_ids:
154
                raise CLISyntaxError('Usage: <id1|id1-id2> [id3|id3-id4] ...',
155
                    details=self.__doc__.split('\n'))
156
            return foo(self, cmd_ids, *args, **kwargs)
157
        return _raise
158

    
159

    
160
class cyclades(object):
161
    about_flavor_id = [
162
        'How to pick a valid flavor id:',
163
        '* get a list of flavor ids: /flavor list',
164
        '* details of flavor: /flavor info <flavor id>']
165

    
166
    about_network_id = [
167
        'How to pick a valid network id:',
168
        '* get a list of network ids: /network list',
169
        '* details of network: /network info <network id>']
170

    
171
    @classmethod
172
    def connection(this, foo):
173
        return generic._connection(foo, 'compute.url')
174

    
175
    @classmethod
176
    def date(this, foo):
177
        def _raise(self, *args, **kwargs):
178
            try:
179
                return foo(self, *args, **kwargs)
180
            except ClientError as ce:
181
                if ce.status == 400 and 'changes-since' in ('%s' % ce):
182
                    raise CLIError(
183
                        'Incorrect date format for --since',
184
                        details=['Accepted date format: d/m/y'])
185
                raise
186
        return _raise
187

    
188
    @classmethod
189
    def network_id(this, foo):
190
        def _raise(self, *args, **kwargs):
191
            network_id = kwargs.get('network_id', None)
192
            try:
193
                network_id = int(network_id)
194
                return foo(self, *args, **kwargs)
195
            except ValueError as ve:
196
                raiseCLIError(ve, 'Invalid network id %s ' % network_id,
197
                    details='network id must be a positive integer',
198
                    importance=1)
199
            except ClientError as ce:
200
                if network_id and ce.status == 404 and\
201
                    'network' in ('%s' % ce).lower():
202
                        raiseCLIError(ce,
203
                            'No network with id %s found' % network_id,
204
                            details=this.about_network_id)
205
                raise
206
        return _raise
207

    
208
    @classmethod
209
    def network_max(this, foo):
210
        def _raise(self, *args, **kwargs):
211
            try:
212
                return foo(self, *args, **kwargs)
213
            except ClientError as ce:
214
                if ce.status == 413:
215
                    raiseCLIError(ce,
216
                        'Cannot create another network',
217
                        details=['Maximum number of networks reached',
218
                            '* to get a list of networks: /network list',
219
                            '* to delete a network: /network delete <net id>'])
220
                raise
221
        return _raise
222

    
223
    @classmethod
224
    def network_in_use(this, foo):
225
        def _raise(self, *args, **kwargs):
226
            network_id = kwargs.get('network_id', None)
227
            try:
228
                return foo(self, *args, **kwargs)
229
            except ClientError as ce:
230
                if network_id or ce.status == 421:
231
                    raiseCLIError(ce,
232
                        'Network with id %s is in use' % network_id,
233
                        details=[
234
                            'Disconnect all nics/VMs of this network first',
235
                            '* to get nics: /network info %s' % network_id,
236
                            '.  (under "attachments" section)',
237
                            '* to disconnect: /network disconnect <nic id>'])
238
                raise
239
        return _raise
240

    
241
    @classmethod
242
    def flavor_id(this, foo):
243
        def _raise(self, *args, **kwargs):
244
            flavor_id = kwargs.get('flavor_id', None)
245
            try:
246
                flavor_id = int(flavor_id)
247
                return foo(self, *args, **kwargs)
248
            except ValueError as ve:
249
                raiseCLIError(ve, 'Invalid flavor id %s ' % flavor_id,
250
                    details='Flavor id must be a positive integer',
251
                    importance=1)
252
            except ClientError as ce:
253
                if flavor_id and ce.status == 404 and\
254
                    'flavor' in ('%s' % ce).lower():
255
                        raiseCLIError(ce,
256
                            'No flavor with id %s found' % flavor_id,
257
                            details=this.about_flavor_id)
258
                raise
259
        return _raise
260

    
261
    @classmethod
262
    def server_id(this, foo):
263
        def _raise(self, *args, **kwargs):
264
            server_id = kwargs.get('server_id', None)
265
            try:
266
                server_id = int(server_id)
267
                return foo(self, *args, **kwargs)
268
            except ValueError as ve:
269
                raiseCLIError(ve,
270
                    'Invalid server(VM) id %s' % server_id,
271
                    details=['id must be a positive integer'],
272
                    importance=1)
273
            except ClientError as ce:
274
                err_msg = ('%s' % ce).lower()
275
                if (ce.status == 404 and 'server' in err_msg)\
276
                or (ce.status == 400 and 'not found' in err_msg):
277
                    raiseCLIError(ce,
278
                        'server(VM) with id %s not found' % server_id,
279
                        details=[
280
                            '* to get existing VM ids: /server list',
281
                            '* to get VM details: /server info <VM id>'])
282
                raise
283
        return _raise
284

    
285
    @classmethod
286
    def firewall(this, foo):
287
        def _raise(self, *args, **kwargs):
288
            profile = kwargs.get('profile', None)
289
            try:
290
                return foo(self, *args, **kwargs)
291
            except ClientError as ce:
292
                if ce.status == 400 and profile\
293
                and 'firewall' in ('%s' % ce).lower():
294
                    raiseCLIError(ce,
295
                        '%s is an invalid firewall profile term' % profile,
296
                        details=['Try one of the following:',
297
                            '* DISABLED: Shutdown firewall',
298
                            '* ENABLED: Firewall in normal mode',
299
                            '* PROTECTED: Firewall in secure mode'])
300
                raise
301
        return _raise
302

    
303
    @classmethod
304
    def nic_id(this, foo):
305
        def _raise(self, *args, **kwargs):
306
            try:
307
                return foo(self, *args, **kwargs)
308
            except ClientError as ce:
309
                nic_id = kwargs.get('nic_id', None)
310
                if nic_id and ce.status == 404\
311
                and 'network interface' in ('%s' % ce).lower():
312
                    server_id = kwargs.get('server_id', '<no server>')
313
                    err_msg = 'No nic %s on server(VM) with id %s' % (
314
                        nic_id,
315
                        server_id)
316
                    raiseCLIError(ce, err_msg, details=[
317
                        '* check server(VM) with id %s: /server info %s' % (
318
                            server_id,
319
                            server_id),
320
                        '* list nics for server(VM) with id %s:' % server_id,
321
                        '      /server addr %s' % server_id])
322
                raise
323
        return _raise
324

    
325
    @classmethod
326
    def nic_format(this, foo):
327
        def _raise(self, *args, **kwargs):
328
            try:
329
                return foo(self, *args, **kwargs)
330
            except IndexError as ie:
331
                nic_id = kwargs.get('nic_id', None)
332
                raiseCLIError(ie,
333
                    'Invalid format for network interface (nic) %s' % nic_id,
334
                    importance=1,
335
                    details=[
336
                        'nid_id format: nic-<server id>-<nic id>',
337
                        '* get nics of a network: /network info <net id>',
338
                        '    (listed the "attachments" section)'])
339
        return _raise
340

    
341
    @classmethod
342
    def metadata(this, foo):
343
        def _raise(self, *args, **kwargs):
344
            key = kwargs.get('key', None)
345
            try:
346
                foo(self, *args, **kwargs)
347
            except ClientError as ce:
348
                if key and ce.status == 404\
349
                    and 'metadata' in ('%s' % ce).lower():
350
                        raiseCLIError(ce, 'No VM metadata with key %s' % key)
351
                raise
352
        return _raise
353

    
354

    
355
class plankton(object):
356

    
357
    about_image_id = ['How to pick a suitable image:',
358
        '* get a list of image ids: /image list',
359
        '* details of image: /flavor info <image id>']
360

    
361
    @classmethod
362
    def connection(this, foo):
363
        return generic._connection(foo, 'image.url')
364

    
365
    @classmethod
366
    def id(this, foo):
367
        def _raise(self, *args, **kwargs):
368
            image_id = kwargs.get('image_id', None)
369
            try:
370
                foo(self, *args, **kwargs)
371
            except ClientError as ce:
372
                if image_id and (ce.status == 404\
373
                    or (ce.status == 400 and
374
                        'image not found' in ('%s' % ce).lower())\
375
                    or ce.status == 411):
376
                        raiseCLIError(ce,
377
                            'No image with id %s found' % image_id,
378
                            details=this.about_image_id)
379
                raise
380
        return _raise
381

    
382
    @classmethod
383
    def metadata(this, foo):
384
        def _raise(self, *args, **kwargs):
385
            key = kwargs.get('key', None)
386
            try:
387
                foo(self, *args, **kwargs)
388
            except ClientError as ce:
389
                if ce.status == 404 or ((ce.status == 400\
390
                    and 'metadata' in ('%s' % ce).lower())):
391
                        raiseCLIError(ce,
392
                            'No properties with key %s in this image' % key)
393
                raise
394
        return _raise
395

    
396

    
397
class pithos(object):
398
    container_howto = ['To specify a container:',
399
    '  1. Set store.container variable (permanent)',
400
    '     /config set store.container <container>',
401
    '  2. --container=<container> (temporary, overrides 1)',
402
    '  3. Use the container:path format (temporary, overrides all)',
403
    'For a list of containers: /store list']
404

    
405
    @classmethod
406
    def connection(this, foo):
407
        return generic._connection(foo, 'store.url')
408

    
409
    @classmethod
410
    def quota(this, foo):
411
        def _raise(self, *args, **kwargs):
412
            try:
413
                return foo(self, *args, **kwargs)
414
            except ClientError as ce:
415
                if ce.status == 413:
416
                    raiseCLIError(ce, 'User quota exceeded', details=[
417
                        '* get quotas:',
418
                        '  * upper total limit:      /store quota',
419
                        '  * container limit:  /store quota <container>',
420
                        '* set a higher quota (if permitted):',
421
                        '    /store setquota <quota>[unit] <container>'
422
                        '    as long as <container quota> <= <total quota>'])
423
                raise
424
        return _raise
425

    
426
    @classmethod
427
    def container(this, foo):
428
        def _raise(self, *args, **kwargs):
429
            dst_cont = kwargs.get('dst_cont', None)
430
            try:
431
                return foo(self, *args, **kwargs)
432
            except ClientError as ce:
433
                if ce.status == 404 and 'container' in ('%s' % ce).lower():
434
                        cont = '%s or %s' % (self.container, dst_cont)\
435
                        if dst_cont else self.container
436
                        raiseCLIError(ce,
437
                            'Is container %s in account %s ?' % (
438
                                cont,
439
                                self.account),
440
                            details=this.container_howto)
441
                raise
442
        return _raise
443

    
444
    @classmethod
445
    def object_path(this, foo):
446
        def _raise(self, *args, **kwargs):
447
            try:
448
                return foo(self, *args, **kwargs)
449
            except ClientError as ce:
450
                err_msg = ('%s' % ce).lower()
451
                if ce.status == 404 and 'object not found' in err_msg:
452
                    raiseCLIError(ce,
453
                        'No object %s in %s\'s container %s'\
454
                        % (self.path, self.account, self.container),
455
                        details=this.container_howto)
456
                raise
457
        return _raise
458

    
459
    @classmethod
460
    def object_size(this, foo):
461
        def _raise(self, *args, **kwargs):
462
            size = kwargs.get('size', None)
463
            start = kwargs.get('start', 0)
464
            end = kwargs.get('end', 0)
465
            if size:
466
                try:
467
                    size = int(size)
468
                except ValueError as ve:
469
                    raiseCLIError(ve,
470
                        'Invalid file size %s ' % size,
471
                        details=['size must be a positive integer'],
472
                        importance=1)
473
            else:
474
                try:
475
                    start = int(start)
476
                except ValueError as e:
477
                    raiseCLIError(e,
478
                        'Invalid start value %s in range' % start,
479
                        details=['size must be a positive integer'],
480
                        importance=1)
481
                try:
482
                    end = int(end)
483
                except ValueError as e:
484
                    raiseCLIError(e,
485
                        'Invalid end value %s in range' % end,
486
                        details=['size must be a positive integer'],
487
                        importance=1)
488
                if start > end:
489
                    raiseCLIError(
490
                        'Invalid range %s-%s' % (start, end),
491
                        details=['size must be a positive integer'],
492
                        importance=1)
493
                size = end - start
494
            try:
495
                return foo(self, *args, **kwargs)
496
            except ClientError as ce:
497
                err_msg = ('%s' % ce).lower()
498
                if size and (ce.status == 416 or
499
                (ce.status == 400 and\
500
                    'object length is smaller than range length' in err_msg)):
501
                    raiseCLIError(ce,
502
                        'Remote object %s:%s <= %s %s' % (
503
                            self.container,
504
                            self.path,
505
                            format_size(size),
506
                            ('(%sB)' % size) if size >= 1024 else ''))
507
                raise
508
        return _raise