Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (20.5 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
import logging
36

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

    
42
sendlog = logging.getLogger('clients.send')
43
datasendlog = logging.getLogger('data.send')
44
recvlog = logging.getLogger('clients.recv')
45
datarecvlog = logging.getLogger('data.recv')
46

    
47

    
48
class generic(object):
49

    
50
    @classmethod
51
    def all(this, foo):
52
        def _raise(self, *args, **kwargs):
53
            try:
54
                return foo(self, *args, **kwargs)
55
            except Exception as e:
56
                if _debug:
57
                    print_stack()
58
                    print_exc(e)
59
                raiseCLIError(e)
60
        return _raise
61

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

    
94

    
95
class astakos(object):
96

    
97
    _token_details = [
98
        'To check default token: /config get token',
99
        'If set/update a token:',
100
        '*  (permanent):    /config set token <token>',
101
        '*  (temporary):    re-run with <token> parameter']
102

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

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

    
139

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

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

    
159

    
160
class cyclades(object):
161
    about_flavor_id = [
162
        'How to pick a valid flavor id:',
163
        '* get a list of flavor ids: /flavor list',
164
        '* details of flavor: /flavor info <flavor id>']
165

    
166
    about_network_id = [
167
        'How to pick a valid network id:',
168
        '* get a list of network ids: /network list',
169
        '* details of network: /network info <network id>']
170

    
171
    @classmethod
172
    def connection(this, foo):
173
        return generic._connection(foo, 'compute.url')
174

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

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

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

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

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

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

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

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

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

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

    
358

    
359
class plankton(object):
360

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

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

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

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

    
400

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

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

    
413
    @classmethod
414
    def quota(this, foo):
415
        def _raise(self, *args, **kwargs):
416
            try:
417
                return foo(self, *args, **kwargs)
418
            except ClientError as ce:
419
                if ce.status == 413:
420
                    raiseCLIError(ce, 'User quota exceeded', details=[
421
                        '* get quotas:',
422
                        '  * upper total limit:      /store quota',
423
                        '  * container limit:  /store quota <container>',
424
                        '* set a higher quota (if permitted):',
425
                        '    /store setquota <quota>[unit] <container>'
426
                        '    as long as <container quota> <= <total quota>'])
427
                raise
428
        return _raise
429

    
430
    @classmethod
431
    def container(this, foo):
432
        def _raise(self, *args, **kwargs):
433
            dst_cont = kwargs.get('dst_cont', None)
434
            try:
435
                return foo(self, *args, **kwargs)
436
            except ClientError as ce:
437
                if ce.status == 404 and 'container' in ('%s' % ce).lower():
438
                        cont = '%s or %s' % (self.container, dst_cont)\
439
                        if dst_cont else self.container
440
                        raiseCLIError(ce,
441
                            'Is container %s in account %s ?' % (
442
                                cont,
443
                                self.account),
444
                            details=this.container_howto)
445
                raise
446
        return _raise
447

    
448
    @classmethod
449
    def local_path(this, foo):
450
        def _raise(self, *args, **kwargs):
451
            local_path = kwargs.get('local_path', '<None>')
452
            try:
453
                return foo(self, *args, **kwargs)
454
            except IOError as ioe:
455
                raiseCLIError(ioe,
456
                    'Failed to access file %s' % local_path,
457
                    importance=2)
458
        return _raise
459

    
460
    @classmethod
461
    def object_path(this, foo):
462
        def _raise(self, *args, **kwargs):
463
            try:
464
                return foo(self, *args, **kwargs)
465
            except ClientError as ce:
466
                err_msg = ('%s' % ce).lower()
467
                if (ce.status == 404 or ce.status == 500)\
468
                and 'object' in err_msg and 'not' in err_msg:
469
                    raiseCLIError(ce,
470
                        'No object %s in %s\'s container %s'\
471
                        % (self.path, self.account, self.container),
472
                        details=this.container_howto)
473
                raise
474
        return _raise
475

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