Statistics
| Branch: | Tag: | Revision:

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

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

    
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

    
42
class generic(object):
43

    
44
    @classmethod
45
    def all(this, foo):
46
        def _raise(self, *args, **kwargs):
47
            try:
48
                return foo(self, *args, **kwargs)
49
            except Exception as e:
50
                if _debug:
51
                    print_stack()
52
                    print_exc(e)
53
                raiseCLIError(e)
54
        return _raise
55

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

    
87

    
88
class user(object):
89

    
90
    _token_details = [
91
        'To check default token: /config get token',
92
        'If set/update a token:',
93
        '*  (permanent):    /config set token <token>',
94
        '*  (temporary):    re-run with <token> parameter']
95

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

    
116
    @classmethod
117
    def authenticate(this, foo):
118
        def _raise(self, *args, **kwargs):
119
            try:
120
                return foo(self, *args, **kwargs)
121
            except ClientError as ce:
122
                if ce.status == 401:
123
                    token = kwargs.get('custom_token', 0) or self.client.token
124
                    msg = (
125
                        'Authorization failed for token %s' % token
126
                    ) if token else 'No token provided',
127
                    details = [] if token else this._token_details
128
                    raiseCLIError(ce, msg, details=details)
129
            self._raise = foo
130
        return _raise
131

    
132

    
133
class history(object):
134
    @classmethod
135
    def init(this, foo):
136
        def _raise(self, *args, **kwargs):
137
            r = foo(self, *args, **kwargs)
138
            if not hasattr(self, 'history'):
139
                raise CLIError('Failed to load history', importance=2)
140
            return r
141
        return _raise
142

    
143
    @classmethod
144
    def _get_cmd_ids(this, foo):
145
        def _raise(self, cmd_ids, *args, **kwargs):
146
            if not cmd_ids:
147
                raise CLISyntaxError(
148
                    'Usage: <id1|id1-id2> [id3|id3-id4] ...',
149
                    details=self.__doc__.split('\n'))
150
            return foo(self, cmd_ids, *args, **kwargs)
151
        return _raise
152

    
153

    
154
class cyclades(object):
155
    about_flavor_id = [
156
        'How to pick a valid flavor id:',
157
        '* get a list of flavor ids: /flavor list',
158
        '* details of flavor: /flavor info <flavor id>']
159

    
160
    about_network_id = [
161
        'How to pick a valid network id:',
162
        '* get a list of network ids: /network list',
163
        '* details of network: /network info <network id>']
164

    
165
    @classmethod
166
    def connection(this, foo):
167
        return generic._connection(foo, 'compute.url')
168

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

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

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

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

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

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

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

    
302
    @classmethod
303
    def nic_id(this, foo):
304
        def _raise(self, *args, **kwargs):
305
            try:
306
                return foo(self, *args, **kwargs)
307
            except ClientError as ce:
308
                nic_id = kwargs.get('nic_id', None)
309
                if nic_id and ce.status == 404 and (
310
                    'network interface' in ('%s' % ce).lower()
311
                ):
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
                msg = 'Invalid format for network interface (nic) %s' % nic_id
333
                raiseCLIError(ie, msg, importance=1, details=[
334
                    'nid_id format: nic-<server id>-<nic id>',
335
                    '* get nics of a network: /network info <net id>',
336
                    '    (listed the "attachments" section)'])
337
        return _raise
338

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

    
353

    
354
class plankton(object):
355

    
356
    about_image_id = [
357
        '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
    remote_image_file = [
362
        'Suggested usage:',
363
        '  /image register <image container>:<uploaded image file path>',
364
        'To set "image" as image container and "my_dir/img.diskdump" as',
365
        'the remote image file path, try one of the following:',
366
        '- <image container>:<remote path>',
367
        '    e.g. image:/my_dir/img.diskdump',
368
        '- <remote path> -C <image container>',
369
        '    e.g. /my_dir/img.diskdump -C image',
370
        'To check if the image file is accessible to current user:',
371
        '  /file list <image container>',
372
        'If the file is located under a different user id "us3r1d"',
373
        ' use the --fileowner=us3r1d  argument e.g.:',
374
        '  /image register "my" image:my_dir/img.diskdump --fileowner=us3r1d',
375
        'Note: The form pithos://<userid>/<container>/<path> is deprecated']
376

    
377
    @classmethod
378
    def connection(this, foo):
379
        return generic._connection(foo, 'image.url')
380

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

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

    
415
    @classmethod
416
    def image_file(this, foo):
417
        def _raise(self, name, container_path):
418
            try:
419
                return foo(self, name, container_path)
420
            except ClientError as ce:
421
                if ce.status in (400,):
422
                    raiseCLIError(
423
                        ce,
424
                        'Nonexistent location for %s' % container_path,
425
                        importance=2, details=this.remote_image_file)
426
                raise
427
        return _raise
428

    
429

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

    
439
    @classmethod
440
    def connection(this, foo):
441
        return generic._connection(foo, 'file.url')
442

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

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

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

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

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

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