Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / errors.py @ 81c60832

History | View | Annotate | Download (22.1 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

    
36
from kamaki.clients import ClientError
37
from kamaki.cli.errors import CLIError, raiseCLIError, CLISyntaxError
38
from kamaki.cli import _debug, kloger
39
from kamaki.cli.utils import format_size
40

    
41
CLOUDNAME = [
42
    'Note: If you use a named cloud, use its name instead of "default"']
43

    
44

    
45
class generic(object):
46

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

    
94

    
95
class user(object):
96

    
97
    _token_details = [
98
        'To check default token: /config get cloud.default.token',
99
        'If set/update a token:',
100
        '*  (permanent):  /config set cloud.default.token <token>',
101
        '*  (temporary):  re-run with <token> parameter'] + CLOUDNAME
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:'
114
                    ' kamaki config set cloud.default.token <tkn>)')
115
            if not getattr(client, 'base_url', False):
116
                msg = 'Missing synnefo authentication URL'
117
                raise CLIError(msg, importance=3, details=[
118
                    'Check if authentication URL is correct',
119
                        '  check current URL:',
120
                        '    /config get cloud.default.url',
121
                        '  set new auth. URL:',
122
                        '    /config set cloud.default.url'] + CLOUDNAME)
123
            return r
124
        return _raise
125

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

    
143

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

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

    
164

    
165
class cyclades(object):
166
    about_flavor_id = [
167
        'How to pick a valid flavor id:',
168
        '* get a list of flavor ids: /flavor list',
169
        '* details of flavor: /flavor info <flavor id>']
170

    
171
    about_network_id = [
172
        'How to pick a valid network id:',
173
        '* get a list of network ids: /network list',
174
        '* details of network: /network info <network id>']
175

    
176
    @classmethod
177
    def connection(this, foo):
178
        return generic._connection(foo)
179

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

    
193
    @classmethod
194
    def cluster_size(this, foo):
195
        def _raise(self, *args, **kwargs):
196
            size = kwargs.get('size', None)
197
            try:
198
                size = int(size)
199
                assert size > 0, 'Cluster size must be a positive integer'
200
                return foo(self, *args, **kwargs)
201
            except ValueError as ve:
202
                msg = 'Invalid cluster size value %s' % size
203
                raiseCLIError(ve, msg, importance=1, details=[
204
                    'Cluster size must be a positive integer'])
205
            except AssertionError as ae:
206
                raiseCLIError(
207
                    ae, 'Invalid cluster size %s' % size, importance=1)
208
            except ClientError:
209
                raise
210
        return _raise
211

    
212
    @classmethod
213
    def network_id(this, foo):
214
        def _raise(self, *args, **kwargs):
215
            network_id = kwargs.get('network_id', None)
216
            try:
217
                network_id = int(network_id)
218
                return foo(self, *args, **kwargs)
219
            except ValueError as ve:
220
                msg = 'Invalid network id %s ' % network_id
221
                details = 'network id must be a positive integer'
222
                raiseCLIError(ve, msg, details=details, importance=1)
223
            except ClientError as ce:
224
                if network_id and ce.status == 404 and (
225
                    'network' in ('%s' % ce).lower()
226
                ):
227
                    msg = 'No network with id %s found' % network_id,
228
                    raiseCLIError(ce, msg, details=this.about_network_id)
229
                raise
230
        return _raise
231

    
232
    @classmethod
233
    def network_max(this, foo):
234
        def _raise(self, *args, **kwargs):
235
            try:
236
                return foo(self, *args, **kwargs)
237
            except ClientError as ce:
238
                if ce.status == 413:
239
                    msg = 'Cannot create another network',
240
                    details = [
241
                        'Maximum number of networks reached',
242
                        '* to get a list of networks: /network list',
243
                        '* to delete a network: /network delete <net id>']
244
                    raiseCLIError(ce, msg, details=details)
245
                raise
246
        return _raise
247

    
248
    @classmethod
249
    def network_in_use(this, foo):
250
        def _raise(self, *args, **kwargs):
251
            network_id = kwargs.get('network_id', None)
252
            try:
253
                return foo(self, *args, **kwargs)
254
            except ClientError as ce:
255
                if network_id and ce.status in (400, ):
256
                    msg = 'Network with id %s does not exist' % network_id,
257
                    raiseCLIError(ce, msg, details=this.about_network_id)
258
                elif network_id or ce.status in (421, ):
259
                    msg = 'Network with id %s is in use' % network_id,
260
                    raiseCLIError(ce, msg, details=[
261
                        'Disconnect all nics/servers of this network first',
262
                        '* to get nics: /network info %s' % network_id,
263
                        '.  (under "attachments" section)',
264
                        '* to disconnect: /network disconnect <nic id>'])
265
                raise
266
        return _raise
267

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

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

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

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

    
355
    @classmethod
356
    def nic_format(this, foo):
357
        def _raise(self, *args, **kwargs):
358
            try:
359
                return foo(self, *args, **kwargs)
360
            except IndexError as ie:
361
                nic_id = kwargs.get('nic_id', None)
362
                msg = 'Invalid format for network interface (nic) %s' % nic_id
363
                raiseCLIError(ie, msg, importance=1, details=[
364
                    'nid_id format: nic-<server id>-<nic id>',
365
                    '* get nics of a network: /network info <net id>',
366
                    '    (listed the "attachments" section)'])
367
        return _raise
368

    
369
    @classmethod
370
    def metadata(this, foo):
371
        def _raise(self, *args, **kwargs):
372
            key = kwargs.get('key', None)
373
            try:
374
                foo(self, *args, **kwargs)
375
            except ClientError as ce:
376
                if key and ce.status == 404 and (
377
                    'metadata' in ('%s' % ce).lower()
378
                ):
379
                        raiseCLIError(
380
                            ce, 'No v. server metadata with key %s' % key)
381
                raise
382
        return _raise
383

    
384

    
385
class plankton(object):
386

    
387
    about_image_id = [
388
        'How to pick a suitable image:',
389
        '* get a list of image ids: /image list',
390
        '* details of image: /image meta <image id>']
391

    
392
    @classmethod
393
    def connection(this, foo):
394
        return generic._connection(foo)
395

    
396
    @classmethod
397
    def id(this, foo):
398
        def _raise(self, *args, **kwargs):
399
            image_id = kwargs.get('image_id', None)
400
            try:
401
                foo(self, *args, **kwargs)
402
            except ClientError as ce:
403
                if image_id and (
404
                    ce.status == 404
405
                    or (
406
                        ce.status == 400
407
                        and 'image not found' in ('%s' % ce).lower())
408
                    or ce.status == 411
409
                ):
410
                        msg = 'No image with id %s found' % image_id
411
                        raiseCLIError(ce, msg, details=this.about_image_id)
412
                raise
413
        return _raise
414

    
415
    @classmethod
416
    def metadata(this, foo):
417
        def _raise(self, *args, **kwargs):
418
            key = kwargs.get('key', None)
419
            try:
420
                return foo(self, *args, **kwargs)
421
            except ClientError as ce:
422
                ce_msg = ('%s' % ce).lower()
423
                if ce.status == 404 or (
424
                        ce.status == 400 and 'metadata' in ce_msg):
425
                    msg = 'No properties with key %s in this image' % key
426
                    raiseCLIError(ce, msg)
427
                raise
428
        return _raise
429

    
430

    
431
class pithos(object):
432
    container_howto = [
433
        'To specify a container:',
434
        '  1. --container=<container> (temporary, overrides all)',
435
        '  2. Use the container:path format (temporary, overrides 3)',
436
        '  3. Set pithos_container variable (permanent)',
437
        '     /config set pithos_container <container>',
438
        'For a list of containers: /file list']
439

    
440
    @classmethod
441
    def connection(this, foo):
442
        return generic._connection(foo)
443

    
444
    @classmethod
445
    def account(this, foo):
446
        def _raise(self, *args, **kwargs):
447
            try:
448
                return foo(self, *args, **kwargs)
449
            except ClientError as ce:
450
                if ce.status == 403:
451
                    raiseCLIError(
452
                        ce,
453
                        'Invalid account credentials for this operation',
454
                        details=['Check user account settings'])
455
                raise
456
        return _raise
457

    
458
    @classmethod
459
    def quota(this, foo):
460
        def _raise(self, *args, **kwargs):
461
            try:
462
                return foo(self, *args, **kwargs)
463
            except ClientError as ce:
464
                if ce.status == 413:
465
                    raiseCLIError(ce, 'User quota exceeded', details=[
466
                        '* get quotas:',
467
                        '  * upper total limit:      /file quota',
468
                        '  * container limit:',
469
                        '    /file containerlimit get <container>',
470
                        '* set a higher container limit:',
471
                        '    /file containerlimit set <limit> <container>'])
472
                raise
473
        return _raise
474

    
475
    @classmethod
476
    def container(this, foo):
477
        def _raise(self, *args, **kwargs):
478
            dst_cont = kwargs.get('dst_cont', None)
479
            try:
480
                return foo(self, *args, **kwargs)
481
            except ClientError as ce:
482
                if ce.status == 404 and 'container' in ('%s' % ce).lower():
483
                        cont = ('%s or %s' % (
484
                            self.container,
485
                            dst_cont)) if dst_cont else self.container
486
                        msg = 'Is container %s in current account?' % (cont),
487
                        raiseCLIError(ce, msg, details=this.container_howto)
488
                raise
489
        return _raise
490

    
491
    @classmethod
492
    def local_path(this, foo):
493
        def _raise(self, *args, **kwargs):
494
            local_path = kwargs.get('local_path', '<None>')
495
            try:
496
                return foo(self, *args, **kwargs)
497
            except IOError as ioe:
498
                msg = 'Failed to access file %s' % local_path,
499
                raiseCLIError(ioe, msg, importance=2)
500
        return _raise
501

    
502
    @classmethod
503
    def object_path(this, foo):
504
        def _raise(self, *args, **kwargs):
505
            try:
506
                return foo(self, *args, **kwargs)
507
            except ClientError as ce:
508
                err_msg = ('%s' % ce).lower()
509
                if (
510
                    ce.status == 404 or ce.status == 500
511
                ) and 'object' in err_msg and 'not' in err_msg:
512
                    msg = 'No object %s in container %s' % (
513
                        self.path,
514
                        self.container)
515
                    raiseCLIError(ce, msg, details=this.container_howto)
516
                raise
517
        return _raise
518

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