Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / errors.py @ 602888f4

History | View | Annotate | Download (21.8 kB)

1
# Copyright 2011-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.command
33

    
34
from traceback import print_stack, print_exc
35
from astakosclient import AstakosClientException
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
CLOUDNAME = ['Note: Set a cloud and use its name instead of "default"']
43

    
44

    
45
class generic(object):
46

    
47
    @classmethod
48
    def all(this, func):
49
        def _raise(self, *args, **kwargs):
50
            try:
51
                return func(self, *args, **kwargs)
52
            except Exception as e:
53
                if _debug:
54
                    print_stack()
55
                    print_exc(e)
56
                if isinstance(e, CLIError) or isinstance(e, ClientError):
57
                    raiseCLIError(e)
58
                raiseCLIError(e, details=['%s, -d for debug info' % type(e)])
59
        return _raise
60

    
61
    @classmethod
62
    def _connection(this, func):
63
        def _raise(self, *args, **kwargs):
64
            try:
65
                func(self, *args, **kwargs)
66
            except ClientError as ce:
67
                ce_msg = ('%s' % ce).lower()
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',
72
                        '  $ kamaki user authenticate',
73
                        '  # to set token:',
74
                        '  $ kamaki config set cloud.default.token <token>',
75
                        '  # to get current token:',
76
                        '  $ kamaki config get cloud.default.token',
77
                    ] + CLOUDNAME)
78
                elif ce.status in range(-12, 200) + [302, 401, 500]:
79
                    raiseCLIError(ce, importance=3, details=[
80
                        'Check if service is up'])
81
                elif ce.status == 404 and 'kamakihttpresponse' in ce_msg:
82
                    client = getattr(self, 'client', None)
83
                    if not client:
84
                        raise
85
                    url = getattr(client, 'base_url', '<empty>')
86
                    msg = 'Invalid service URL %s' % url
87
                    raiseCLIError(ce, msg, details=[
88
                        'Check if authentication URL is correct',
89
                        '  # check current URL',
90
                        '  $ kamaki config get cloud.default.url',
91
                        '  # set new authentication URL',
92
                        '  $ kamaki config set cloud.default.url'] + CLOUDNAME)
93
                raise
94
        return _raise
95

    
96

    
97
class user(object):
98

    
99
    _token_details = [
100
        'To check default token: /config get cloud.default.token',
101
        'If set/update a token:',
102
        '  #  (permanent)',
103
        '  $ kamaki config set cloud.default.token <token>'] + CLOUDNAME
104

    
105
    @classmethod
106
    def astakosclient(this, func):
107
        def _raise(self, *args, **kwargs):
108
            try:
109
                r = func(self, *args, **kwargs)
110
            except AstakosClientException as ace:
111
                raiseCLIError(ace, 'Error in synnefo-AstakosClient')
112
            return r
113
        return _raise
114

    
115
    @classmethod
116
    def load(this, func):
117
        def _raise(self, *args, **kwargs):
118
            r = func(self, *args, **kwargs)
119
            try:
120
                client = getattr(self, 'client')
121
            except AttributeError as ae:
122
                raiseCLIError(ae, 'Client setup failure', importance=3)
123
            if not getattr(client, 'token', False):
124
                kloger.warning(
125
                    'No permanent token (try:'
126
                    ' kamaki config set cloud.default.token <tkn>)')
127
            if not getattr(client, 'astakos_base_url', False):
128
                msg = 'Missing synnefo authentication URL'
129
                raise CLIError(msg, importance=3, details=[
130
                    'Check if authentication URL is correct',
131
                    '  # check current URL:',
132
                    '  $ kamaki config get cloud.default.url',
133
                    '  # set new auth. URL:',
134
                    '  $ kamaki config set cloud.default.url'] + CLOUDNAME)
135
            return r
136
        return _raise
137

    
138
    @classmethod
139
    def authenticate(this, func):
140
        def _raise(self, *args, **kwargs):
141
            try:
142
                return func(self, *args, **kwargs)
143
            except (ClientError, AstakosClientException) as ce:
144
                if ce.status == 401:
145
                    token = kwargs.get('custom_token', 0) or self.client.token
146
                    msg = ('Authorization failed for token %s' % token) if (
147
                        token) else 'No token provided',
148
                    details = [] if token else this._token_details
149
                    raiseCLIError(ce, msg, details=details)
150
                raise ce
151
            self._raise = func
152
        return _raise
153

    
154

    
155
class history(object):
156
    @classmethod
157
    def init(this, func):
158
        def _raise(self, *args, **kwargs):
159
            r = func(self, *args, **kwargs)
160
            if not hasattr(self, 'history'):
161
                raise CLIError('Failed to load history', importance=2)
162
            return r
163
        return _raise
164

    
165
    @classmethod
166
    def _get_cmd_ids(this, func):
167
        def _raise(self, cmd_ids, *args, **kwargs):
168
            if not cmd_ids:
169
                raise CLISyntaxError(
170
                    'Usage: <id1|id1-id2> [id3|id3-id4] ...',
171
                    details=self.__doc__.split('\n'))
172
            return func(self, cmd_ids, *args, **kwargs)
173
        return _raise
174

    
175

    
176
class cyclades(object):
177
    about_flavor_id = [
178
        'How to pick a valid flavor id:',
179
        '  # get a list of flavor ids',
180
        '  $ kamaki flavor list',
181
        '  # details of flavor',
182
        '  $ kamaki flavor info <flavor id>',
183
        '',
184
    ]
185

    
186
    about_network_id = [
187
        'How to pick a valid network id:',
188
        '  # get a list of network ids',
189
        '  $ kamaki network list',
190
        '  # details of network',
191
        '  $ kamaki network info <network id>',
192
        '',
193
    ]
194

    
195
    net_types = ('CUSTOM', 'MAC_FILTERED', 'IP_LESS_ROUTED', 'PHYSICAL_VLAN')
196

    
197
    @classmethod
198
    def connection(this, func):
199
        return generic._connection(func)
200

    
201
    @classmethod
202
    def date(this, func):
203
        def _raise(self, *args, **kwargs):
204
            try:
205
                return func(self, *args, **kwargs)
206
            except ClientError as ce:
207
                if ce.status == 400 and 'changes-since' in ('%s' % ce):
208
                    raise CLIError(
209
                        'Incorrect date format for --since',
210
                        details=['Accepted date format: d/m/y'])
211
                raise
212
        return _raise
213

    
214
    @classmethod
215
    def cluster_size(this, func):
216
        def _raise(self, *args, **kwargs):
217
            size = kwargs.get('size', None)
218
            try:
219
                size = int(size)
220
                assert size > 0, 'Cluster size must be a positive integer'
221
                return func(self, *args, **kwargs)
222
            except ValueError as ve:
223
                msg = 'Invalid cluster size value %s' % size
224
                raiseCLIError(ve, msg, importance=1, details=[
225
                    'Cluster size must be a positive integer'])
226
            except AssertionError as ae:
227
                raiseCLIError(
228
                    ae, 'Invalid cluster size %s' % size, importance=1)
229
            except ClientError:
230
                raise
231
        return _raise
232

    
233
    @classmethod
234
    def network_id(this, func):
235
        def _raise(self, *args, **kwargs):
236
            network_id = kwargs.get('network_id', None)
237
            try:
238
                network_id = int(network_id)
239
                return func(self, *args, **kwargs)
240
            except ValueError as ve:
241
                msg = 'Invalid network id %s ' % network_id
242
                details = 'network id must be a positive integer'
243
                raiseCLIError(ve, msg, details=details, importance=1)
244
            except ClientError as ce:
245
                if network_id and ce.status == 404 and (
246
                    'network' in ('%s' % ce).lower()
247
                ):
248
                    msg = 'No network with id %s found' % network_id,
249
                    raiseCLIError(ce, msg, details=this.about_network_id)
250
                raise
251
        return _raise
252

    
253
    @classmethod
254
    def network_type(this, func):
255
        def _raise(self, *args, **kwargs):
256
            network_type = kwargs.get('network_type', None)
257
            msg = 'Invalid network type %s.\nValid types: %s' % (
258
                network_type, ' '.join(this.net_types))
259
            assert network_type in this.net_types, msg
260
            return func(self, *args, **kwargs)
261
        return _raise
262

    
263
    @classmethod
264
    def flavor_id(this, func):
265
        def _raise(self, *args, **kwargs):
266
            flavor_id = kwargs.get('flavor_id', None)
267
            try:
268
                flavor_id = int(flavor_id)
269
                return func(self, *args, **kwargs)
270
            except ValueError as ve:
271
                msg = 'Invalid flavor id %s ' % flavor_id,
272
                details = 'Flavor id must be a positive integer'
273
                raiseCLIError(ve, msg, details=details, importance=1)
274
            except ClientError as ce:
275
                if flavor_id and ce.status == 404 and (
276
                    'flavor' in ('%s' % ce).lower()
277
                ):
278
                        msg = 'No flavor with id %s found' % flavor_id,
279
                        raiseCLIError(ce, msg, details=this.about_flavor_id)
280
                raise
281
        return _raise
282

    
283
    @classmethod
284
    def server_id(this, func):
285
        def _raise(self, *args, **kwargs):
286
            server_id = kwargs.get('server_id', None)
287
            try:
288
                server_id = int(server_id)
289
                return func(self, *args, **kwargs)
290
            except ValueError as ve:
291
                msg = 'Invalid virtual server id %s' % server_id,
292
                details = 'Server id must be a positive integer'
293
                raiseCLIError(ve, msg, details=details, importance=1)
294
            except ClientError as ce:
295
                err_msg = ('%s' % ce).lower()
296
                if (
297
                    ce.status == 404 and 'server' in err_msg
298
                ) or (
299
                    ce.status == 400 and 'not found' in err_msg
300
                ):
301
                    msg = 'virtual server with id %s not found' % server_id,
302
                    raiseCLIError(ce, msg, details=[
303
                        '# to get ids of all servers',
304
                        '$ kamaki server list',
305
                        '# to get server details',
306
                        '$ kamaki server info <server id>'])
307
                raise
308
        return _raise
309

    
310
    @classmethod
311
    def firewall(this, func):
312
        def _raise(self, *args, **kwargs):
313
            profile = kwargs.get('profile', None)
314
            try:
315
                return func(self, *args, **kwargs)
316
            except ClientError as ce:
317
                if ce.status == 400 and profile and (
318
                    'firewall' in ('%s' % ce).lower()
319
                ):
320
                    msg = '%s is an invalid firewall profile term' % profile
321
                    raiseCLIError(ce, msg, details=[
322
                        'Try one of the following:',
323
                        '* DISABLED: Shutdown firewall',
324
                        '* ENABLED: Firewall in normal mode',
325
                        '* PROTECTED: Firewall in secure mode'])
326
                raise
327
        return _raise
328

    
329
    @classmethod
330
    def nic_id(this, func):
331
        def _raise(self, *args, **kwargs):
332
            try:
333
                return func(self, *args, **kwargs)
334
            except ClientError as ce:
335
                nic_id = kwargs.get('nic_id', None)
336
                if nic_id and ce.status == 404 and (
337
                    'network interface' in ('%s' % ce).lower()
338
                ):
339
                    server_id = kwargs.get('server_id', '<no server>')
340
                    err_msg = 'No nic %s on virtual server with id %s' % (
341
                        nic_id,
342
                        server_id)
343
                    raiseCLIError(ce, err_msg, details=[
344
                        '* check v. server with id %s: /server info %s' % (
345
                            server_id,
346
                            server_id),
347
                        '* list nics for v. server with id %s:' % server_id,
348
                        '      /server addr %s' % server_id])
349
                raise
350
        return _raise
351

    
352
    @classmethod
353
    def metadata(this, func):
354
        def _raise(self, *args, **kwargs):
355
            key = kwargs.get('key', None)
356
            try:
357
                func(self, *args, **kwargs)
358
            except ClientError as ce:
359
                if key and ce.status == 404 and (
360
                    'metadata' in ('%s' % ce).lower()
361
                ):
362
                        raiseCLIError(
363
                            ce, 'No virtual server metadata with key %s' % key)
364
                raise
365
        return _raise
366

    
367

    
368
class plankton(object):
369

    
370
    about_image_id = [
371
        'How to pick a suitable image:',
372
        '  # get a list of image ids',
373
        '  $ kamaki image list',
374
        '  # details of an image',
375
        '  $ kamaki image meta <image id>',
376
        '',
377
    ]
378

    
379
    @classmethod
380
    def connection(this, func):
381
        return generic._connection(func)
382

    
383
    @classmethod
384
    def id(this, func):
385
        def _raise(self, *args, **kwargs):
386
            image_id = kwargs.get('image_id', None)
387
            try:
388
                func(self, *args, **kwargs)
389
            except ClientError as ce:
390
                if image_id and (
391
                    ce.status == 404
392
                    or (
393
                        ce.status == 400
394
                        and 'image not found' in ('%s' % ce).lower())
395
                    or ce.status == 411
396
                ):
397
                        msg = 'No image with id %s found' % image_id
398
                        raiseCLIError(ce, msg, details=this.about_image_id)
399
                raise
400
        return _raise
401

    
402
    @classmethod
403
    def metadata(this, func):
404
        def _raise(self, *args, **kwargs):
405
            key = kwargs.get('key', None)
406
            try:
407
                return func(self, *args, **kwargs)
408
            except ClientError as ce:
409
                ce_msg = ('%s' % ce).lower()
410
                if ce.status == 404 or (
411
                        ce.status == 400 and 'metadata' in ce_msg):
412
                    msg = 'No properties with key %s in this image' % key
413
                    raiseCLIError(ce, msg)
414
                raise
415
        return _raise
416

    
417

    
418
class pithos(object):
419
    container_howto = [
420
        'Use a / to refer to a container (default: /pithos) e.g.,',
421
        '  # list the contents of container "images"',
422
        '  $ kamaki file list /images',
423
        '  # get information on file "my.img" in container "images"',
424
        '  $ kamaki file info /images/my.img',
425
        '',
426
        'To get a list of containers:',
427
        '  $ kamaki container list',
428
        '',
429
    ]
430

    
431
    @classmethod
432
    def connection(this, func):
433
        return generic._connection(func)
434

    
435
    @classmethod
436
    def account(this, func):
437
        def _raise(self, *args, **kwargs):
438
            try:
439
                return func(self, *args, **kwargs)
440
            except ClientError as ce:
441
                if ce.status == 403:
442
                    raiseCLIError(
443
                        ce,
444
                        'Invalid account credentials for this operation',
445
                        details=['Check user account settings'])
446
                raise
447
        return _raise
448

    
449
    @classmethod
450
    def quota(this, func):
451
        def _raise(self, *args, **kwargs):
452
            try:
453
                return func(self, *args, **kwargs)
454
            except ClientError as ce:
455
                if ce.status == 413:
456
                    raiseCLIError(ce, 'User quota exceeded', details=[
457
                        '* get quotas:',
458
                        '  * upper total limit:      /file quota',
459
                        '  * container limit:',
460
                        '    /file containerlimit get <container>',
461
                        '* set a higher container limit:',
462
                        '    /file containerlimit set <limit> <container>'])
463
                raise
464
        return _raise
465

    
466
    @classmethod
467
    def container(this, func):
468
        def _raise(self, *args, **kwargs):
469
            dst_cont = kwargs.get('dst_cont', None)
470
            try:
471
                return func(self, *args, **kwargs)
472
            except ClientError as ce:
473
                if ce.status == 404 and 'container' in ('%s' % ce).lower():
474
                        cont = ('%s or %s' % (
475
                            self.container,
476
                            dst_cont)) if dst_cont else self.container
477
                        msg = 'Container "%s" does not exist' % cont,
478
                        raiseCLIError(ce, msg, details=this.container_howto)
479
                raise
480
        return _raise
481

    
482
    @classmethod
483
    def local_path_download(this, func):
484
        def _raise(self, *args, **kwargs):
485
            try:
486
                return func(self, *args, **kwargs)
487
            except IOError as ioe:
488
                msg = 'Failed to access a local file',
489
                raiseCLIError(ioe, msg, importance=2, details=[
490
                    'Check if the file exists. Also check if the remote',
491
                    'directories exist. All directories in a remote path',
492
                    'must exist to succesfully download a container or a',
493
                    'directory.',
494
                    'To create a remote directory:',
495
                    '  [kamaki] file mkdir REMOTE_DIRECTORY_PATH'])
496
        return _raise
497

    
498
    @classmethod
499
    def local_path(this, func):
500
        def _raise(self, *args, **kwargs):
501
            local_path = kwargs.get('local_path', None)
502
            try:
503
                return func(self, *args, **kwargs)
504
            except IOError as ioe:
505
                msg = 'Failed to access file %s' % local_path,
506
                raiseCLIError(ioe, msg, importance=2)
507
        return _raise
508

    
509
    @classmethod
510
    def object_path(this, func):
511
        def _raise(self, *args, **kwargs):
512
            try:
513
                return func(self, *args, **kwargs)
514
            except ClientError as ce:
515
                err_msg = ('%s' % ce).lower()
516
                if (
517
                    ce.status == 404 or ce.status == 500
518
                ) and 'object' in err_msg and 'not' in err_msg:
519
                    msg = 'No object %s in container %s' % (
520
                        self.path,
521
                        self.container)
522
                    raiseCLIError(ce, msg, details=this.container_howto)
523
                raise
524
        return _raise
525

    
526
    @classmethod
527
    def object_size(this, func):
528
        def _raise(self, *args, **kwargs):
529
            size = kwargs.get('size', None)
530
            start = kwargs.get('start', 0)
531
            end = kwargs.get('end', 0)
532
            if size:
533
                try:
534
                    size = int(size)
535
                except ValueError as ve:
536
                    msg = 'Invalid file size %s ' % size
537
                    details = ['size must be a positive integer']
538
                    raiseCLIError(ve, msg, details=details, importance=1)
539
            else:
540
                try:
541
                    start = int(start)
542
                except ValueError as e:
543
                    msg = 'Invalid start value %s in range' % start,
544
                    details = ['size must be a positive integer'],
545
                    raiseCLIError(e, msg, details=details, importance=1)
546
                try:
547
                    end = int(end)
548
                except ValueError as e:
549
                    msg = 'Invalid end value %s in range' % end
550
                    details = ['size must be a positive integer']
551
                    raiseCLIError(e, msg, details=details, importance=1)
552
                if start > end:
553
                    raiseCLIError(
554
                        'Invalid range %s-%s' % (start, end),
555
                        details=['size must be a positive integer'],
556
                        importance=1)
557
                size = end - start
558
            try:
559
                return func(self, *args, **kwargs)
560
            except ClientError as ce:
561
                err_msg = ('%s' % ce).lower()
562
                expected = 'object length is smaller than range length'
563
                if size and (
564
                    ce.status == 416 or (
565
                        ce.status == 400 and expected in err_msg)):
566
                    raiseCLIError(ce, 'Remote object %s:%s <= %s %s' % (
567
                        self.container, self.path, format_size(size),
568
                        ('(%sB)' % size) if size >= 1024 else ''))
569
                raise
570
        return _raise