errors.py 20.9 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# Copyright 2011-2012 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
#   1. Redistributions of source code must retain the above
#      copyright notice, this list of conditions and the following
#      disclaimer.
#
#   2. Redistributions in binary form must reproduce the above
#      copyright notice, this list of conditions and the following
#      disclaimer in the documentation and/or other materials
#      provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.command

from traceback import print_stack, print_exc

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
43
44
45
46
47
48
49
50
51
52
53
54
55


class generic(object):

    @classmethod
    def all(this, foo):
        def _raise(self, *args, **kwargs):
            try:
                return foo(self, *args, **kwargs)
            except Exception as e:
                if _debug:
                    print_stack()
                    print_exc(e)
                raiseCLIError(e)
        return _raise

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

87

88
class user(object):
89
90

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

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

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


class history(object):
    @classmethod
    def init(this, foo):
        def _raise(self, *args, **kwargs):
            r = foo(self, *args, **kwargs)
            if not hasattr(self, 'history'):
                raise CLIError('Failed to load history', importance=2)
            return r
        return _raise

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

153
154

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

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

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

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

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

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

    @classmethod
    def network_in_use(this, foo):
        def _raise(self, *args, **kwargs):
            network_id = kwargs.get('network_id', None)
            try:
                return foo(self, *args, **kwargs)
            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
230
231
232
233
234
                    msg = 'Network with id %s is in use' % network_id,
                    raiseCLIError(ce, msg, details=[
                        'Disconnect all nics/VMs of this network first',
                        '* to get nics: /network info %s' % network_id,
                        '.  (under "attachments" section)',
                        '* to disconnect: /network disconnect <nic id>'])
Stavros Sachtouris's avatar
Stavros Sachtouris committed
235
236
237
                raise
        return _raise

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

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

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

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

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

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

353
354
355

class plankton(object):

356
357
    about_image_id = [
        'How to pick a suitable image:',
358
359
        '* get a list of image ids: /image list',
        '* details of image: /flavor info <image id>']
360
361
362
363
364
365
366

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

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

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


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

    @classmethod
    def connection(this, foo):
411
        return generic._connection(foo, 'file.url')
412

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

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

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

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

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

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