Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / errors.py @ 60bcb377

History | View | Annotate | Download (23.7 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 = [
43
    'Note: If you use a named cloud, use its name instead of "default"']
44

    
45

    
46
class generic(object):
47

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

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

    
95

    
96
class user(object):
97

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

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

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

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

    
153

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

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

    
174

    
175
class cyclades(object):
176
    about_flavor_id = [
177
        'How to pick a valid flavor id:',
178
        '* get a list of flavor ids: /flavor list',
179
        '* details of flavor: /flavor info <flavor id>']
180

    
181
    about_network_id = [
182
        'How to pick a valid network id:',
183
        '* get a list of network ids: /network list',
184
        '* details of network: /network info <network id>']
185

    
186
    net_types = ('CUSTOM', 'MAC_FILTERED', 'IP_LESS_ROUTED', 'PHYSICAL_VLAN')
187

    
188
    @classmethod
189
    def connection(this, func):
190
        return generic._connection(func)
191

    
192
    @classmethod
193
    def date(this, func):
194
        def _raise(self, *args, **kwargs):
195
            try:
196
                return func(self, *args, **kwargs)
197
            except ClientError as ce:
198
                if ce.status == 400 and 'changes-since' in ('%s' % ce):
199
                    raise CLIError(
200
                        'Incorrect date format for --since',
201
                        details=['Accepted date format: d/m/y'])
202
                raise
203
        return _raise
204

    
205
    @classmethod
206
    def cluster_size(this, func):
207
        def _raise(self, *args, **kwargs):
208
            size = kwargs.get('size', None)
209
            try:
210
                size = int(size)
211
                assert size > 0, 'Cluster size must be a positive integer'
212
                return func(self, *args, **kwargs)
213
            except ValueError as ve:
214
                msg = 'Invalid cluster size value %s' % size
215
                raiseCLIError(ve, msg, importance=1, details=[
216
                    'Cluster size must be a positive integer'])
217
            except AssertionError as ae:
218
                raiseCLIError(
219
                    ae, 'Invalid cluster size %s' % size, importance=1)
220
            except ClientError:
221
                raise
222
        return _raise
223

    
224
    @classmethod
225
    def network_id(this, func):
226
        def _raise(self, *args, **kwargs):
227
            network_id = kwargs.get('network_id', None)
228
            try:
229
                network_id = int(network_id)
230
                return func(self, *args, **kwargs)
231
            except ValueError as ve:
232
                msg = 'Invalid network id %s ' % network_id
233
                details = 'network id must be a positive integer'
234
                raiseCLIError(ve, msg, details=details, importance=1)
235
            except ClientError as ce:
236
                if network_id and ce.status == 404 and (
237
                    'network' in ('%s' % ce).lower()
238
                ):
239
                    msg = 'No network with id %s found' % network_id,
240
                    raiseCLIError(ce, msg, details=this.about_network_id)
241
                raise
242
        return _raise
243

    
244
    @classmethod
245
    def network_type(this, func):
246
        def _raise(self, *args, **kwargs):
247
            network_type = kwargs.get('network_type', None)
248
            msg = 'Invalid network type %s.\nValid types: %s' % (
249
                network_type, ' '.join(this.net_types))
250
            assert network_type in this.net_types, msg
251
            return func(self, *args, **kwargs)
252
        return _raise
253

    
254
    @classmethod
255
    def network_max(this, func):
256
        def _raise(self, *args, **kwargs):
257
            try:
258
                return func(self, *args, **kwargs)
259
            except ClientError as ce:
260
                if ce.status == 413:
261
                    msg = 'Cannot create another network',
262
                    details = [
263
                        'Maximum number of networks reached',
264
                        '* to get a list of networks: /network list',
265
                        '* to delete a network: /network delete <net id>']
266
                    raiseCLIError(ce, msg, details=details)
267
                raise
268
        return _raise
269

    
270
    @classmethod
271
    def network_in_use(this, func):
272
        def _raise(self, *args, **kwargs):
273
            network_id = kwargs.get('network_id', None)
274
            try:
275
                return func(self, *args, **kwargs)
276
            except ClientError as ce:
277
                if network_id and ce.status in (400, ):
278
                    msg = 'Network with id %s does not exist' % network_id,
279
                    raiseCLIError(ce, msg, details=this.about_network_id)
280
                elif network_id or ce.status in (421, ):
281
                    msg = 'Network with id %s is in use' % network_id,
282
                    raiseCLIError(ce, msg, details=[
283
                        'Disconnect all nics/servers of this network first',
284
                        '* to get nics: /network info %s' % network_id,
285
                        '.  (under "attachments" section)',
286
                        '* to disconnect: /network disconnect <nic id>'])
287
                raise
288
        return _raise
289

    
290
    @classmethod
291
    def flavor_id(this, func):
292
        def _raise(self, *args, **kwargs):
293
            flavor_id = kwargs.get('flavor_id', None)
294
            try:
295
                flavor_id = int(flavor_id)
296
                return func(self, *args, **kwargs)
297
            except ValueError as ve:
298
                msg = 'Invalid flavor id %s ' % flavor_id,
299
                details = 'Flavor id must be a positive integer'
300
                raiseCLIError(ve, msg, details=details, importance=1)
301
            except ClientError as ce:
302
                if flavor_id and ce.status == 404 and (
303
                    'flavor' in ('%s' % ce).lower()
304
                ):
305
                        msg = 'No flavor with id %s found' % flavor_id,
306
                        raiseCLIError(ce, msg, details=this.about_flavor_id)
307
                raise
308
        return _raise
309

    
310
    @classmethod
311
    def server_id(this, func):
312
        def _raise(self, *args, **kwargs):
313
            server_id = kwargs.get('server_id', None)
314
            try:
315
                server_id = int(server_id)
316
                return func(self, *args, **kwargs)
317
            except ValueError as ve:
318
                msg = 'Invalid virtual server id %s' % server_id,
319
                details = 'Server id must be a positive integer'
320
                raiseCLIError(ve, msg, details=details, importance=1)
321
            except ClientError as ce:
322
                err_msg = ('%s' % ce).lower()
323
                if (
324
                    ce.status == 404 and 'server' in err_msg
325
                ) or (
326
                    ce.status == 400 and 'not found' in err_msg
327
                ):
328
                    msg = 'virtual server with id %s not found' % server_id,
329
                    raiseCLIError(ce, msg, details=[
330
                        '* to get ids of all servers: /server list',
331
                        '* to get server details: /server info <server id>'])
332
                raise
333
        return _raise
334

    
335
    @classmethod
336
    def firewall(this, func):
337
        def _raise(self, *args, **kwargs):
338
            profile = kwargs.get('profile', None)
339
            try:
340
                return func(self, *args, **kwargs)
341
            except ClientError as ce:
342
                if ce.status == 400 and profile and (
343
                    'firewall' in ('%s' % ce).lower()
344
                ):
345
                    msg = '%s is an invalid firewall profile term' % profile
346
                    raiseCLIError(ce, msg, details=[
347
                        'Try one of the following:',
348
                        '* DISABLED: Shutdown firewall',
349
                        '* ENABLED: Firewall in normal mode',
350
                        '* PROTECTED: Firewall in secure mode'])
351
                raise
352
        return _raise
353

    
354
    @classmethod
355
    def nic_id(this, func):
356
        def _raise(self, *args, **kwargs):
357
            try:
358
                return func(self, *args, **kwargs)
359
            except ClientError as ce:
360
                nic_id = kwargs.get('nic_id', None)
361
                if nic_id and ce.status == 404 and (
362
                    'network interface' in ('%s' % ce).lower()
363
                ):
364
                    server_id = kwargs.get('server_id', '<no server>')
365
                    err_msg = 'No nic %s on virtual server with id %s' % (
366
                        nic_id,
367
                        server_id)
368
                    raiseCLIError(ce, err_msg, details=[
369
                        '* check v. server with id %s: /server info %s' % (
370
                            server_id,
371
                            server_id),
372
                        '* list nics for v. server with id %s:' % server_id,
373
                        '      /server addr %s' % server_id])
374
                raise
375
        return _raise
376

    
377
    @classmethod
378
    def nic_format(this, func):
379
        def _raise(self, *args, **kwargs):
380
            try:
381
                return func(self, *args, **kwargs)
382
            except IndexError as ie:
383
                nic_id = kwargs.get('nic_id', None)
384
                msg = 'Invalid format for network interface (nic) %s' % nic_id
385
                raiseCLIError(ie, msg, importance=1, details=[
386
                    'nid_id format: nic-<server id>-<nic id>',
387
                    '* get nics of a network: /network info <net id>',
388
                    '    (listed the "attachments" section)'])
389
        return _raise
390

    
391
    @classmethod
392
    def metadata(this, func):
393
        def _raise(self, *args, **kwargs):
394
            key = kwargs.get('key', None)
395
            try:
396
                func(self, *args, **kwargs)
397
            except ClientError as ce:
398
                if key and ce.status == 404 and (
399
                    'metadata' in ('%s' % ce).lower()
400
                ):
401
                        raiseCLIError(
402
                            ce, 'No virtual server metadata with key %s' % key)
403
                raise
404
        return _raise
405

    
406

    
407
class plankton(object):
408

    
409
    about_image_id = [
410
        'How to pick a suitable image:',
411
        '* get a list of image ids: /image list',
412
        '* details of image: /image meta <image id>']
413

    
414
    @classmethod
415
    def connection(this, func):
416
        return generic._connection(func)
417

    
418
    @classmethod
419
    def id(this, func):
420
        def _raise(self, *args, **kwargs):
421
            image_id = kwargs.get('image_id', None)
422
            try:
423
                func(self, *args, **kwargs)
424
            except ClientError as ce:
425
                if image_id and (
426
                    ce.status == 404
427
                    or (
428
                        ce.status == 400
429
                        and 'image not found' in ('%s' % ce).lower())
430
                    or ce.status == 411
431
                ):
432
                        msg = 'No image with id %s found' % image_id
433
                        raiseCLIError(ce, msg, details=this.about_image_id)
434
                raise
435
        return _raise
436

    
437
    @classmethod
438
    def metadata(this, func):
439
        def _raise(self, *args, **kwargs):
440
            key = kwargs.get('key', None)
441
            try:
442
                return func(self, *args, **kwargs)
443
            except ClientError as ce:
444
                ce_msg = ('%s' % ce).lower()
445
                if ce.status == 404 or (
446
                        ce.status == 400 and 'metadata' in ce_msg):
447
                    msg = 'No properties with key %s in this image' % key
448
                    raiseCLIError(ce, msg)
449
                raise
450
        return _raise
451

    
452

    
453
class pithos(object):
454
    container_howto = [
455
        'To specify a container:',
456
        '  1. --container=<container> (temporary, overrides all)',
457
        '  2. Use the container:path format (temporary, overrides 3)',
458
        '  3. Set pithos_container variable (permanent)',
459
        '     /config set pithos_container <container>',
460
        'For a list of containers: /file list']
461

    
462
    @classmethod
463
    def connection(this, func):
464
        return generic._connection(func)
465

    
466
    @classmethod
467
    def account(this, func):
468
        def _raise(self, *args, **kwargs):
469
            try:
470
                return func(self, *args, **kwargs)
471
            except ClientError as ce:
472
                if ce.status == 403:
473
                    raiseCLIError(
474
                        ce,
475
                        'Invalid account credentials for this operation',
476
                        details=['Check user account settings'])
477
                raise
478
        return _raise
479

    
480
    @classmethod
481
    def quota(this, func):
482
        def _raise(self, *args, **kwargs):
483
            try:
484
                return func(self, *args, **kwargs)
485
            except ClientError as ce:
486
                if ce.status == 413:
487
                    raiseCLIError(ce, 'User quota exceeded', details=[
488
                        '* get quotas:',
489
                        '  * upper total limit:      /file quota',
490
                        '  * container limit:',
491
                        '    /file containerlimit get <container>',
492
                        '* set a higher container limit:',
493
                        '    /file containerlimit set <limit> <container>'])
494
                raise
495
        return _raise
496

    
497
    @classmethod
498
    def container(this, func):
499
        def _raise(self, *args, **kwargs):
500
            dst_cont = kwargs.get('dst_cont', None)
501
            try:
502
                return func(self, *args, **kwargs)
503
            except ClientError as ce:
504
                if ce.status == 404 and 'container' in ('%s' % ce).lower():
505
                        cont = ('%s or %s' % (
506
                            self.container,
507
                            dst_cont)) if dst_cont else self.container
508
                        msg = 'Is container %s in current account?' % (cont),
509
                        raiseCLIError(ce, msg, details=this.container_howto)
510
                raise
511
        return _raise
512

    
513
    @classmethod
514
    def local_path_download(this, func):
515
        def _raise(self, *args, **kwargs):
516
            try:
517
                return func(self, *args, **kwargs)
518
            except IOError as ioe:
519
                msg = 'Failed to access a local file',
520
                raiseCLIError(ioe, msg, importance=2, details=[
521
                    'Check if the file exists. Also check if the remote',
522
                    'directories exist. All directories in a remote path',
523
                    'must exist to succesfully download a container or a',
524
                    'directory.',
525
                    'To create a remote directory:',
526
                    '  [kamaki] file mkdir REMOTE_DIRECTORY_PATH'])
527
        return _raise
528

    
529
    @classmethod
530
    def local_path(this, func):
531
        def _raise(self, *args, **kwargs):
532
            local_path = kwargs.get('local_path', None)
533
            try:
534
                return func(self, *args, **kwargs)
535
            except IOError as ioe:
536
                msg = 'Failed to access file %s' % local_path,
537
                raiseCLIError(ioe, msg, importance=2)
538
        return _raise
539

    
540
    @classmethod
541
    def object_path(this, func):
542
        def _raise(self, *args, **kwargs):
543
            try:
544
                return func(self, *args, **kwargs)
545
            except ClientError as ce:
546
                err_msg = ('%s' % ce).lower()
547
                if (
548
                    ce.status == 404 or ce.status == 500
549
                ) and 'object' in err_msg and 'not' in err_msg:
550
                    msg = 'No object %s in container %s' % (
551
                        self.path,
552
                        self.container)
553
                    raiseCLIError(ce, msg, details=this.container_howto)
554
                raise
555
        return _raise
556

    
557
    @classmethod
558
    def object_size(this, func):
559
        def _raise(self, *args, **kwargs):
560
            size = kwargs.get('size', None)
561
            start = kwargs.get('start', 0)
562
            end = kwargs.get('end', 0)
563
            if size:
564
                try:
565
                    size = int(size)
566
                except ValueError as ve:
567
                    msg = 'Invalid file size %s ' % size
568
                    details = ['size must be a positive integer']
569
                    raiseCLIError(ve, msg, details=details, importance=1)
570
            else:
571
                try:
572
                    start = int(start)
573
                except ValueError as e:
574
                    msg = 'Invalid start value %s in range' % start,
575
                    details = ['size must be a positive integer'],
576
                    raiseCLIError(e, msg, details=details, importance=1)
577
                try:
578
                    end = int(end)
579
                except ValueError as e:
580
                    msg = 'Invalid end value %s in range' % end
581
                    details = ['size must be a positive integer']
582
                    raiseCLIError(e, msg, details=details, importance=1)
583
                if start > end:
584
                    raiseCLIError(
585
                        'Invalid range %s-%s' % (start, end),
586
                        details=['size must be a positive integer'],
587
                        importance=1)
588
                size = end - start
589
            try:
590
                return func(self, *args, **kwargs)
591
            except ClientError as ce:
592
                err_msg = ('%s' % ce).lower()
593
                expected = 'object length is smaller than range length'
594
                if size and (
595
                    ce.status == 416 or (
596
                        ce.status == 400 and expected in err_msg)):
597
                    raiseCLIError(ce, 'Remote object %s:%s <= %s %s' % (
598
                        self.container, self.path, format_size(size),
599
                        ('(%sB)' % size) if size >= 1024 else ''))
600
                raise
601
        return _raise