Statistics
| Branch: | Tag: | Revision:

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

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

    
45

    
46
class generic(object):
47

    
48
    @classmethod
49
    def all(this, foo):
50
        def _raise(self, *args, **kwargs):
51
            try:
52
                return foo(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, foo):
64
        def _raise(self, *args, **kwargs):
65
            try:
66
                foo(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 remote.default.token <token>',
75
                        '  to get current token:',
76
                        '    /config get remote.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 remote.default.url',
90
                        '  set new auth. url:',
91
                        '    /config set remote.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 remote.default.token',
100
        'If set/update a token:',
101
        '*  (permanent):  /config set remote.default.token <token>',
102
        '*  (temporary):  re-run with <token> parameter'] + CLOUDNAME
103

    
104
    @classmethod
105
    def load(this, foo):
106
        def _raise(self, *args, **kwargs):
107
            r = foo(self, *args, **kwargs)
108
            try:
109
                client = getattr(self, 'client')
110
            except AttributeError as ae:
111
                raiseCLIError(ae, 'Client setup failure', importance=3)
112
            if not getattr(client, 'token', False):
113
                kloger.warning(
114
                    'No permanent token (try:'
115
                    ' kamaki config set remote.default.token <tkn>)')
116
            if not getattr(client, 'base_url', False):
117
                msg = 'Missing synnefo authentication URL'
118
                raise CLIError(msg, importance=3, details=[
119
                    'Check if authentication url is correct',
120
                        '  check current url:',
121
                        '    /config get remote.default.url',
122
                        '  set new auth. url:',
123
                        '    /config set remote.default.url'] + CLOUDNAME)
124
            return r
125
        return _raise
126

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

    
144

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

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

    
165

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

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

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

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

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

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

    
230
    @classmethod
231
    def network_in_use(this, foo):
232
        def _raise(self, *args, **kwargs):
233
            network_id = kwargs.get('network_id', None)
234
            try:
235
                return foo(self, *args, **kwargs)
236
            except ClientError as ce:
237
                if network_id and ce.status == 400:
238
                    msg = 'Network with id %s does not exist' % network_id,
239
                    raiseCLIError(ce, msg, details=this.about_network_id)
240
                elif network_id or ce.status == 421:
241
                    msg = 'Network with id %s is in use' % network_id,
242
                    raiseCLIError(ce, msg, details=[
243
                        'Disconnect all nics/VMs of this network first',
244
                        '* to get nics: /network info %s' % network_id,
245
                        '.  (under "attachments" section)',
246
                        '* to disconnect: /network disconnect <nic id>'])
247
                raise
248
        return _raise
249

    
250
    @classmethod
251
    def flavor_id(this, foo):
252
        def _raise(self, *args, **kwargs):
253
            flavor_id = kwargs.get('flavor_id', None)
254
            try:
255
                flavor_id = int(flavor_id)
256
                return foo(self, *args, **kwargs)
257
            except ValueError as ve:
258
                msg = 'Invalid flavor id %s ' % flavor_id,
259
                details = 'Flavor id must be a positive integer',
260
                raiseCLIError(ve, msg, details=details, importance=1)
261
            except ClientError as ce:
262
                if flavor_id and ce.status == 404 and (
263
                    'flavor' in ('%s' % ce).lower()
264
                ):
265
                        msg = 'No flavor with id %s found' % flavor_id,
266
                        raiseCLIError(ce, msg, details=this.about_flavor_id)
267
                raise
268
        return _raise
269

    
270
    @classmethod
271
    def server_id(this, foo):
272
        def _raise(self, *args, **kwargs):
273
            server_id = kwargs.get('server_id', None)
274
            try:
275
                server_id = int(server_id)
276
                return foo(self, *args, **kwargs)
277
            except ValueError as ve:
278
                msg = 'Invalid server(VM) id %s' % server_id,
279
                details = ['id must be a positive integer'],
280
                raiseCLIError(ve, msg, details=details, importance=1)
281
            except ClientError as ce:
282
                err_msg = ('%s' % ce).lower()
283
                if (
284
                    ce.status == 404 and 'server' in err_msg
285
                ) or (
286
                    ce.status == 400 and 'not found' in err_msg
287
                ):
288
                    msg = 'server(VM) with id %s not found' % server_id,
289
                    raiseCLIError(ce, msg, details=[
290
                        '* to get existing VM ids: /server list',
291
                        '* to get VM details: /server info <VM id>'])
292
                raise
293
        return _raise
294

    
295
    @classmethod
296
    def firewall(this, foo):
297
        def _raise(self, *args, **kwargs):
298
            profile = kwargs.get('profile', None)
299
            try:
300
                return foo(self, *args, **kwargs)
301
            except ClientError as ce:
302
                if ce.status == 400 and profile and (
303
                    'firewall' in ('%s' % ce).lower()
304
                ):
305
                    msg = '%s is an invalid firewall profile term' % profile
306
                    raiseCLIError(ce, msg, details=[
307
                        'Try one of the following:',
308
                        '* DISABLED: Shutdown firewall',
309
                        '* ENABLED: Firewall in normal mode',
310
                        '* PROTECTED: Firewall in secure mode'])
311
                raise
312
        return _raise
313

    
314
    @classmethod
315
    def nic_id(this, foo):
316
        def _raise(self, *args, **kwargs):
317
            try:
318
                return foo(self, *args, **kwargs)
319
            except ClientError as ce:
320
                nic_id = kwargs.get('nic_id', None)
321
                if nic_id and ce.status == 404 and (
322
                    'network interface' in ('%s' % ce).lower()
323
                ):
324
                    server_id = kwargs.get('server_id', '<no server>')
325
                    err_msg = 'No nic %s on server(VM) with id %s' % (
326
                        nic_id,
327
                        server_id)
328
                    raiseCLIError(ce, err_msg, details=[
329
                        '* check server(VM) with id %s: /server info %s' % (
330
                            server_id,
331
                            server_id),
332
                        '* list nics for server(VM) with id %s:' % server_id,
333
                        '      /server addr %s' % server_id])
334
                raise
335
        return _raise
336

    
337
    @classmethod
338
    def nic_format(this, foo):
339
        def _raise(self, *args, **kwargs):
340
            try:
341
                return foo(self, *args, **kwargs)
342
            except IndexError as ie:
343
                nic_id = kwargs.get('nic_id', None)
344
                msg = 'Invalid format for network interface (nic) %s' % nic_id
345
                raiseCLIError(ie, msg, importance=1, details=[
346
                    'nid_id format: nic-<server id>-<nic id>',
347
                    '* get nics of a network: /network info <net id>',
348
                    '    (listed the "attachments" section)'])
349
        return _raise
350

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

    
365

    
366
class plankton(object):
367

    
368
    about_image_id = [
369
        'How to pick a suitable image:',
370
        '* get a list of image ids: /image list',
371
        '* details of image: /flavor info <image id>']
372

    
373
    @classmethod
374
    def connection(this, foo):
375
        return generic._connection(foo)
376

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

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

    
411

    
412
class pithos(object):
413
    container_howto = [
414
        'To specify a container:',
415
        '  1. --container=<container> (temporary, overrides all)',
416
        '  2. Use the container:path format (temporary, overrides 3)',
417
        '  3. Set pithos_container variable (permanent)',
418
        '     /config set pithos_container <container>',
419
        'For a list of containers: /file list']
420

    
421
    @classmethod
422
    def connection(this, foo):
423
        return generic._connection(foo)
424

    
425
    @classmethod
426
    def account(this, foo):
427
        def _raise(self, *args, **kwargs):
428
            try:
429
                return foo(self, *args, **kwargs)
430
            except ClientError as ce:
431
                if ce.status == 403:
432
                    raiseCLIError(
433
                        ce,
434
                        'Invalid account credentials for this operation',
435
                        details=['Check user account settings'])
436
                raise
437
        return _raise
438

    
439
    @classmethod
440
    def quota(this, foo):
441
        def _raise(self, *args, **kwargs):
442
            try:
443
                return foo(self, *args, **kwargs)
444
            except ClientError as ce:
445
                if ce.status == 413:
446
                    raiseCLIError(ce, 'User quota exceeded', details=[
447
                        '* get quotas:',
448
                        '  * upper total limit:      /file quota',
449
                        '  * container limit:',
450
                        '    /file containerlimit get <container>',
451
                        '* set a higher container limit:',
452
                        '    /file containerlimit set <limit> <container>'])
453
                raise
454
        return _raise
455

    
456
    @classmethod
457
    def container(this, foo):
458
        def _raise(self, *args, **kwargs):
459
            dst_cont = kwargs.get('dst_cont', None)
460
            try:
461
                return foo(self, *args, **kwargs)
462
            except ClientError as ce:
463
                if ce.status == 404 and 'container' in ('%s' % ce).lower():
464
                        cont = ('%s or %s' % (
465
                            self.container,
466
                            dst_cont)) if dst_cont else self.container
467
                        msg = 'Is container %s in current account?' % (cont),
468
                        raiseCLIError(ce, msg, details=this.container_howto)
469
                raise
470
        return _raise
471

    
472
    @classmethod
473
    def local_path(this, foo):
474
        def _raise(self, *args, **kwargs):
475
            local_path = kwargs.get('local_path', '<None>')
476
            try:
477
                return foo(self, *args, **kwargs)
478
            except IOError as ioe:
479
                msg = 'Failed to access file %s' % local_path,
480
                raiseCLIError(ioe, msg, importance=2)
481
        return _raise
482

    
483
    @classmethod
484
    def object_path(this, foo):
485
        def _raise(self, *args, **kwargs):
486
            try:
487
                return foo(self, *args, **kwargs)
488
            except ClientError as ce:
489
                err_msg = ('%s' % ce).lower()
490
                if (
491
                    ce.status == 404 or ce.status == 500
492
                ) and 'object' in err_msg and 'not' in err_msg:
493
                    msg = 'No object %s in container %s' % (
494
                        self.path,
495
                        self.container)
496
                    raiseCLIError(ce, msg, details=this.container_howto)
497
                raise
498
        return _raise
499

    
500
    @classmethod
501
    def object_size(this, foo):
502
        def _raise(self, *args, **kwargs):
503
            size = kwargs.get('size', None)
504
            start = kwargs.get('start', 0)
505
            end = kwargs.get('end', 0)
506
            if size:
507
                try:
508
                    size = int(size)
509
                except ValueError as ve:
510
                    msg = 'Invalid file size %s ' % size
511
                    details = ['size must be a positive integer']
512
                    raiseCLIError(ve, msg, details=details, importance=1)
513
            else:
514
                try:
515
                    start = int(start)
516
                except ValueError as e:
517
                    msg = 'Invalid start value %s in range' % start,
518
                    details = ['size must be a positive integer'],
519
                    raiseCLIError(e, msg, details=details, importance=1)
520
                try:
521
                    end = int(end)
522
                except ValueError as e:
523
                    msg = 'Invalid end value %s in range' % end
524
                    details = ['size must be a positive integer']
525
                    raiseCLIError(e, msg, details=details, importance=1)
526
                if start > end:
527
                    raiseCLIError(
528
                        'Invalid range %s-%s' % (start, end),
529
                        details=['size must be a positive integer'],
530
                        importance=1)
531
                size = end - start
532
            try:
533
                return foo(self, *args, **kwargs)
534
            except ClientError as ce:
535
                err_msg = ('%s' % ce).lower()
536
                expected = 'object length is smaller than range length'
537
                if size and (
538
                    ce.status == 416 or (
539
                        ce.status == 400 and expected in err_msg)):
540
                    raiseCLIError(ce, 'Remote object %s:%s <= %s %s' % (
541
                        self.container, self.path, format_size(size),
542
                        ('(%sB)' % size) if size >= 1024 else ''))
543
                raise
544
        return _raise