Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / errors.py @ 4018326d

History | View | Annotate | Download (21.1 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
                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: /config set [.server.]token <token>',
74
                        '  to get current token: /config get [server.]token'])
75
                elif ce.status in range(-12, 200) + [302, 401, 403, 500]:
76
                    raiseCLIError(ce, importance=3, details=[
77
                        'Check if service is up or set to url %s' % base_url,
78
                        '  to get url: /config get %s' % base_url,
79
                        '  to set url: /config set %s <URL>' % base_url])
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
                        'Please, check if service url is correctly set',
88
                        '* to get current url: /config get compute.url',
89
                        '* to set url: /config set compute.url <URL>'])
90
                raise
91
        return _raise
92

    
93

    
94
class user(object):
95

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

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

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

    
138

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

    
149
    @classmethod
150
    def _get_cmd_ids(this, foo):
151
        def _raise(self, cmd_ids, *args, **kwargs):
152
            if not cmd_ids:
153
                raise CLISyntaxError(
154
                    '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
                msg = 'Invalid network id %s ' % network_id
197
                details = ['network id must be a positive integer']
198
                raiseCLIError(ve, msg, details=details, importance=1)
199
            except ClientError as ce:
200
                if network_id and ce.status == 404 and (
201
                    'network' in ('%s' % ce).lower()
202
                ):
203
                    msg = 'No network with id %s found' % network_id,
204
                    raiseCLIError(ce, msg, 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
                    msg = 'Cannot create another network',
216
                    details = [
217
                        'Maximum number of networks reached',
218
                        '* to get a list of networks: /network list',
219
                        '* to delete a network: /network delete <net id>']
220
                    raiseCLIError(ce, msg, details=details)
221
                raise
222
        return _raise
223

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

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

    
264
    @classmethod
265
    def server_id(this, foo):
266
        def _raise(self, *args, **kwargs):
267
            server_id = kwargs.get('server_id', None)
268
            try:
269
                server_id = int(server_id)
270
                return foo(self, *args, **kwargs)
271
            except ValueError as ve:
272
                msg = 'Invalid server(VM) id %s' % server_id,
273
                details = ['id must be a positive integer'],
274
                raiseCLIError(ve, msg, details=details, importance=1)
275
            except ClientError as ce:
276
                err_msg = ('%s' % ce).lower()
277
                if (
278
                    ce.status == 404 and 'server' in err_msg
279
                ) or (
280
                    ce.status == 400 and 'not found' in err_msg
281
                ):
282
                    msg = 'server(VM) with id %s not found' % server_id,
283
                    raiseCLIError(ce, msg, 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 and (
297
                    'firewall' in ('%s' % ce).lower()
298
                ):
299
                    msg = '%s is an invalid firewall profile term' % profile
300
                    raiseCLIError(ce, msg, details=[
301
                        'Try one of the following:',
302
                        '* DISABLED: Shutdown firewall',
303
                        '* ENABLED: Firewall in normal mode',
304
                        '* PROTECTED: Firewall in secure mode'])
305
                raise
306
        return _raise
307

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

    
331
    @classmethod
332
    def nic_format(this, foo):
333
        def _raise(self, *args, **kwargs):
334
            try:
335
                return foo(self, *args, **kwargs)
336
            except IndexError as ie:
337
                nic_id = kwargs.get('nic_id', None)
338
                msg = 'Invalid format for network interface (nic) %s' % nic_id
339
                raiseCLIError(ie, msg, importance=1, 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 and (
353
                    'metadata' in ('%s' % ce).lower()
354
                ):
355
                        raiseCLIError(ce, 'No VM metadata with key %s' % key)
356
                raise
357
        return _raise
358

    
359

    
360
class plankton(object):
361

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

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

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

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

    
405

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

    
415
    @classmethod
416
    def connection(this, foo):
417
        return generic._connection(foo, 'store.url')
418

    
419
    @classmethod
420
    def account(this, foo):
421
        def _raise(self, *args, **kwargs):
422
            try:
423
                return foo(self, *args, **kwargs)
424
            except ClientError as ce:
425
                if ce.status == 403:
426
                    raiseCLIError(
427
                        ce,
428
                        'Invalid account credentials for this operation',
429
                        details=['Check user account settings'])
430
                raise
431
        return _raise
432

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

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

    
466
    @classmethod
467
    def local_path(this, foo):
468
        def _raise(self, *args, **kwargs):
469
            local_path = kwargs.get('local_path', '<None>')
470
            try:
471
                return foo(self, *args, **kwargs)
472
            except IOError as ioe:
473
                msg = 'Failed to access file %s' % local_path,
474
                raiseCLIError(ioe, msg, importance=2)
475
        return _raise
476

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

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