Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / errors.py @ 8c54338a

History | View | Annotate | Download (20.7 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):
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'])
72
                elif ce.status == 404 and 'kamakihttpresponse' in ce_msg:
73
                    client = getattr(self, 'client', None)
74
                    if not client:
75
                        raise
76
                    url = getattr(client, 'base_url', '<empty>')
77
                    msg = 'Invalid service url %s' % url
78
                    raiseCLIError(ce, msg, details=[
79
                        'Check if authentication url is correct',
80
                        '  check current url:   /config get url',
81
                        '  set new auth. url:   /config set url'])
82
                raise
83
        return _raise
84

    
85

    
86
class user(object):
87

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

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

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

    
131

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

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

    
152

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

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

    
164
    @classmethod
165
    def connection(this, foo):
166
        return generic._connection(foo)
167

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

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

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

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

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

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

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

    
301
    @classmethod
302
    def nic_id(this, foo):
303
        def _raise(self, *args, **kwargs):
304
            try:
305
                return foo(self, *args, **kwargs)
306
            except ClientError as ce:
307
                nic_id = kwargs.get('nic_id', None)
308
                if nic_id and ce.status == 404 and (
309
                    'network interface' in ('%s' % ce).lower()
310
                ):
311
                    server_id = kwargs.get('server_id', '<no server>')
312
                    err_msg = 'No nic %s on server(VM) with id %s' % (
313
                        nic_id,
314
                        server_id)
315
                    raiseCLIError(ce, err_msg, details=[
316
                        '* check server(VM) with id %s: /server info %s' % (
317
                            server_id,
318
                            server_id),
319
                        '* list nics for server(VM) with id %s:' % server_id,
320
                        '      /server addr %s' % server_id])
321
                raise
322
        return _raise
323

    
324
    @classmethod
325
    def nic_format(this, foo):
326
        def _raise(self, *args, **kwargs):
327
            try:
328
                return foo(self, *args, **kwargs)
329
            except IndexError as ie:
330
                nic_id = kwargs.get('nic_id', None)
331
                msg = 'Invalid format for network interface (nic) %s' % nic_id
332
                raiseCLIError(ie, msg, importance=1, details=[
333
                    'nid_id format: nic-<server id>-<nic id>',
334
                    '* get nics of a network: /network info <net id>',
335
                    '    (listed the "attachments" section)'])
336
        return _raise
337

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

    
352

    
353
class plankton(object):
354

    
355
    about_image_id = [
356
        'How to pick a suitable image:',
357
        '* get a list of image ids: /image list',
358
        '* details of image: /flavor info <image id>']
359

    
360
    @classmethod
361
    def connection(this, foo):
362
        return generic._connection(foo)
363

    
364
    @classmethod
365
    def id(this, foo):
366
        def _raise(self, *args, **kwargs):
367
            image_id = kwargs.get('image_id', None)
368
            try:
369
                foo(self, *args, **kwargs)
370
            except ClientError as ce:
371
                if image_id and (
372
                    ce.status == 404
373
                    or (
374
                        ce.status == 400
375
                        and 'image not found' in ('%s' % ce).lower())
376
                    or ce.status == 411
377
                ):
378
                        msg = 'No image with id %s found' % image_id
379
                        raiseCLIError(ce, msg, details=this.about_image_id)
380
                raise
381
        return _raise
382

    
383
    @classmethod
384
    def metadata(this, foo):
385
        def _raise(self, *args, **kwargs):
386
            key = kwargs.get('key', None)
387
            try:
388
                return foo(self, *args, **kwargs)
389
            except ClientError as ce:
390
                ce_msg = ('%s' % ce).lower()
391
                if ce.status == 404 or (
392
                        ce.status == 400 and 'metadata' in ce_msg):
393
                    msg = 'No properties with key %s in this image' % key
394
                    raiseCLIError(ce, msg)
395
                raise
396
        return _raise
397

    
398

    
399
class pithos(object):
400
    container_howto = [
401
        'To specify a container:',
402
        '  1. Set file.container variable (permanent)',
403
        '     /config set file.container <container>',
404
        '  2. --container=<container> (temporary, overrides 1)',
405
        '  3. Use the container:path format (temporary, overrides all)',
406
        'For a list of containers: /file list']
407

    
408
    @classmethod
409
    def connection(this, foo):
410
        return generic._connection(foo)
411

    
412
    @classmethod
413
    def account(this, foo):
414
        def _raise(self, *args, **kwargs):
415
            try:
416
                return foo(self, *args, **kwargs)
417
            except ClientError as ce:
418
                if ce.status == 403:
419
                    raiseCLIError(
420
                        ce,
421
                        'Invalid account credentials for this operation',
422
                        details=['Check user account settings'])
423
                raise
424
        return _raise
425

    
426
    @classmethod
427
    def quota(this, foo):
428
        def _raise(self, *args, **kwargs):
429
            try:
430
                return foo(self, *args, **kwargs)
431
            except ClientError as ce:
432
                if ce.status == 413:
433
                    raiseCLIError(ce, 'User quota exceeded', details=[
434
                        '* get quotas:',
435
                        '  * upper total limit:      /file quota',
436
                        '  * container limit:  /file quota <container>',
437
                        '* set a higher quota (if permitted):',
438
                        '    /file setquota <quota>[unit] <container>'
439
                        '    as long as <container quota> <= <total quota>'])
440
                raise
441
        return _raise
442

    
443
    @classmethod
444
    def container(this, foo):
445
        def _raise(self, *args, **kwargs):
446
            dst_cont = kwargs.get('dst_cont', None)
447
            try:
448
                return foo(self, *args, **kwargs)
449
            except ClientError as ce:
450
                if ce.status == 404 and 'container' in ('%s' % ce).lower():
451
                        cont = ('%s or %s' % (
452
                            self.container,
453
                            dst_cont)) if dst_cont else self.container
454
                        msg = 'Is container %s in current account?' % (cont),
455
                        raiseCLIError(ce, msg, details=this.container_howto)
456
                raise
457
        return _raise
458

    
459
    @classmethod
460
    def local_path(this, foo):
461
        def _raise(self, *args, **kwargs):
462
            local_path = kwargs.get('local_path', '<None>')
463
            try:
464
                return foo(self, *args, **kwargs)
465
            except IOError as ioe:
466
                msg = 'Failed to access file %s' % local_path,
467
                raiseCLIError(ioe, msg, importance=2)
468
        return _raise
469

    
470
    @classmethod
471
    def object_path(this, foo):
472
        def _raise(self, *args, **kwargs):
473
            try:
474
                return foo(self, *args, **kwargs)
475
            except ClientError as ce:
476
                err_msg = ('%s' % ce).lower()
477
                if (
478
                    ce.status == 404 or ce.status == 500
479
                ) and 'object' in err_msg and 'not' in err_msg:
480
                    msg = 'No object %s in container %s' % (
481
                        self.path,
482
                        self.container)
483
                    raiseCLIError(ce, msg, details=this.container_howto)
484
                raise
485
        return _raise
486

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