1 # Copyright 2011-2013 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
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.
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.
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
34 from traceback import print_stack, print_exc
35 from astakosclient import AstakosClientException
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
43 'Note: If you use a named cloud, use its name instead of "default"']
46 class generic(object):
50 def _raise(self, *args, **kwargs):
52 return foo(self, *args, **kwargs)
53 except Exception as e:
57 if isinstance(e, CLIError) or isinstance(e, ClientError):
59 raiseCLIError(e, details=['%s, -d for debug info' % type(e)])
63 def _connection(this, foo):
64 def _raise(self, *args, **kwargs):
66 foo(self, *args, **kwargs)
67 except ClientError as ce:
68 ce_msg = ('%s' % ce).lower()
70 raiseCLIError(ce, 'Authorization failed', details=[
71 'Make sure a valid token is provided:',
72 ' to check if token is valid: /user authenticate',
74 ' /config set cloud.default.token <token>',
75 ' to get current token:',
76 ' /config get cloud.default.token'] + CLOUDNAME)
77 elif ce.status in range(-12, 200) + [302, 401, 403, 500]:
78 raiseCLIError(ce, importance=3, details=[
79 'Check if service is up'])
80 elif ce.status == 404 and 'kamakihttpresponse' in ce_msg:
81 client = getattr(self, 'client', None)
84 url = getattr(client, 'base_url', '<empty>')
85 msg = 'Invalid service URL %s' % url
86 raiseCLIError(ce, msg, details=[
87 'Check if authentication URL is correct',
88 ' check current URL:',
89 ' /config get cloud.default.url',
90 ' set new authentication URL:',
91 ' /config set cloud.default.url'] + CLOUDNAME)
99 'To check default token: /config get cloud.default.token',
100 'If set/update a token:',
101 '* (permanent): /config set cloud.default.token <token>',
102 '* (temporary): re-run with <token> parameter'] + CLOUDNAME
105 def astakosclient(this, foo):
106 def _raise(self, *args, **kwargs):
108 r = foo(self, *args, **kwargs)
109 except AstakosClientException as ace:
110 raiseCLIError(ace, 'Error in synnefo-AstakosClient')
116 def _raise(self, *args, **kwargs):
117 r = foo(self, *args, **kwargs)
119 client = getattr(self, 'client')
120 except AttributeError as ae:
121 raiseCLIError(ae, 'Client setup failure', importance=3)
122 if not getattr(client, 'token', False):
124 'No permanent token (try:'
125 ' kamaki config set cloud.default.token <tkn>)')
126 if not getattr(client, 'astakos_base_url', False):
127 msg = 'Missing synnefo authentication URL'
128 raise CLIError(msg, importance=3, details=[
129 'Check if authentication URL is correct',
130 ' check current URL:',
131 ' /config get cloud.default.url',
132 ' set new auth. URL:',
133 ' /config set cloud.default.url'] + CLOUDNAME)
138 def authenticate(this, foo):
139 def _raise(self, *args, **kwargs):
141 return foo(self, *args, **kwargs)
142 except (ClientError, AstakosClientException) as ce:
144 token = kwargs.get('custom_token', 0) or self.client.token
145 msg = ('Authorization failed for token %s' % token) if (
146 token) else 'No token provided',
147 details = [] if token else this._token_details
148 raiseCLIError(ce, msg, details=details)
154 class history(object):
157 def _raise(self, *args, **kwargs):
158 r = foo(self, *args, **kwargs)
159 if not hasattr(self, 'history'):
160 raise CLIError('Failed to load history', importance=2)
165 def _get_cmd_ids(this, foo):
166 def _raise(self, cmd_ids, *args, **kwargs):
168 raise CLISyntaxError(
169 'Usage: <id1|id1-id2> [id3|id3-id4] ...',
170 details=self.__doc__.split('\n'))
171 return foo(self, cmd_ids, *args, **kwargs)
175 class cyclades(object):
177 'How to pick a valid flavor id:',
178 '* get a list of flavor ids: /flavor list',
179 '* details of flavor: /flavor info <flavor id>']
182 'How to pick a valid network id:',
183 '* get a list of network ids: /network list',
184 '* details of network: /network info <network id>']
187 def connection(this, foo):
188 return generic._connection(foo)
192 def _raise(self, *args, **kwargs):
194 return foo(self, *args, **kwargs)
195 except ClientError as ce:
196 if ce.status == 400 and 'changes-since' in ('%s' % ce):
198 'Incorrect date format for --since',
199 details=['Accepted date format: d/m/y'])
204 def cluster_size(this, foo):
205 def _raise(self, *args, **kwargs):
206 size = kwargs.get('size', None)
209 assert size > 0, 'Cluster size must be a positive integer'
210 return foo(self, *args, **kwargs)
211 except ValueError as ve:
212 msg = 'Invalid cluster size value %s' % size
213 raiseCLIError(ve, msg, importance=1, details=[
214 'Cluster size must be a positive integer'])
215 except AssertionError as ae:
217 ae, 'Invalid cluster size %s' % size, importance=1)
223 def network_id(this, foo):
224 def _raise(self, *args, **kwargs):
225 network_id = kwargs.get('network_id', None)
227 network_id = int(network_id)
228 return foo(self, *args, **kwargs)
229 except ValueError as ve:
230 msg = 'Invalid network id %s ' % network_id
231 details = 'network id must be a positive integer'
232 raiseCLIError(ve, msg, details=details, importance=1)
233 except ClientError as ce:
234 if network_id and ce.status == 404 and (
235 'network' in ('%s' % ce).lower()
237 msg = 'No network with id %s found' % network_id,
238 raiseCLIError(ce, msg, details=this.about_network_id)
243 def network_max(this, foo):
244 def _raise(self, *args, **kwargs):
246 return foo(self, *args, **kwargs)
247 except ClientError as ce:
249 msg = 'Cannot create another network',
251 'Maximum number of networks reached',
252 '* to get a list of networks: /network list',
253 '* to delete a network: /network delete <net id>']
254 raiseCLIError(ce, msg, details=details)
259 def network_in_use(this, foo):
260 def _raise(self, *args, **kwargs):
261 network_id = kwargs.get('network_id', None)
263 return foo(self, *args, **kwargs)
264 except ClientError as ce:
265 if network_id and ce.status in (400, ):
266 msg = 'Network with id %s does not exist' % network_id,
267 raiseCLIError(ce, msg, details=this.about_network_id)
268 elif network_id or ce.status in (421, ):
269 msg = 'Network with id %s is in use' % network_id,
270 raiseCLIError(ce, msg, details=[
271 'Disconnect all nics/servers of this network first',
272 '* to get nics: /network info %s' % network_id,
273 '. (under "attachments" section)',
274 '* to disconnect: /network disconnect <nic id>'])
279 def flavor_id(this, foo):
280 def _raise(self, *args, **kwargs):
281 flavor_id = kwargs.get('flavor_id', None)
283 flavor_id = int(flavor_id)
284 return foo(self, *args, **kwargs)
285 except ValueError as ve:
286 msg = 'Invalid flavor id %s ' % flavor_id,
287 details = 'Flavor id must be a positive integer'
288 raiseCLIError(ve, msg, details=details, importance=1)
289 except ClientError as ce:
290 if flavor_id and ce.status == 404 and (
291 'flavor' in ('%s' % ce).lower()
293 msg = 'No flavor with id %s found' % flavor_id,
294 raiseCLIError(ce, msg, details=this.about_flavor_id)
299 def server_id(this, foo):
300 def _raise(self, *args, **kwargs):
301 server_id = kwargs.get('server_id', None)
303 server_id = int(server_id)
304 return foo(self, *args, **kwargs)
305 except ValueError as ve:
306 msg = 'Invalid virtual server id %s' % server_id,
307 details = 'Server id must be a positive integer'
308 raiseCLIError(ve, msg, details=details, importance=1)
309 except ClientError as ce:
310 err_msg = ('%s' % ce).lower()
312 ce.status == 404 and 'server' in err_msg
314 ce.status == 400 and 'not found' in err_msg
316 msg = 'virtual server with id %s not found' % server_id,
317 raiseCLIError(ce, msg, details=[
318 '* to get ids of all servers: /server list',
319 '* to get server details: /server info <server id>'])
324 def firewall(this, foo):
325 def _raise(self, *args, **kwargs):
326 profile = kwargs.get('profile', None)
328 return foo(self, *args, **kwargs)
329 except ClientError as ce:
330 if ce.status == 400 and profile and (
331 'firewall' in ('%s' % ce).lower()
333 msg = '%s is an invalid firewall profile term' % profile
334 raiseCLIError(ce, msg, details=[
335 'Try one of the following:',
336 '* DISABLED: Shutdown firewall',
337 '* ENABLED: Firewall in normal mode',
338 '* PROTECTED: Firewall in secure mode'])
343 def nic_id(this, foo):
344 def _raise(self, *args, **kwargs):
346 return foo(self, *args, **kwargs)
347 except ClientError as ce:
348 nic_id = kwargs.get('nic_id', None)
349 if nic_id and ce.status == 404 and (
350 'network interface' in ('%s' % ce).lower()
352 server_id = kwargs.get('server_id', '<no server>')
353 err_msg = 'No nic %s on virtual server with id %s' % (
356 raiseCLIError(ce, err_msg, details=[
357 '* check v. server with id %s: /server info %s' % (
360 '* list nics for v. server with id %s:' % server_id,
361 ' /server addr %s' % server_id])
366 def nic_format(this, foo):
367 def _raise(self, *args, **kwargs):
369 return foo(self, *args, **kwargs)
370 except IndexError as ie:
371 nic_id = kwargs.get('nic_id', None)
372 msg = 'Invalid format for network interface (nic) %s' % nic_id
373 raiseCLIError(ie, msg, importance=1, details=[
374 'nid_id format: nic-<server id>-<nic id>',
375 '* get nics of a network: /network info <net id>',
376 ' (listed the "attachments" section)'])
380 def metadata(this, foo):
381 def _raise(self, *args, **kwargs):
382 key = kwargs.get('key', None)
384 foo(self, *args, **kwargs)
385 except ClientError as ce:
386 if key and ce.status == 404 and (
387 'metadata' in ('%s' % ce).lower()
390 ce, 'No v. server metadata with key %s' % key)
395 class plankton(object):
398 'How to pick a suitable image:',
399 '* get a list of image ids: /image list',
400 '* details of image: /image meta <image id>']
403 def connection(this, foo):
404 return generic._connection(foo)
408 def _raise(self, *args, **kwargs):
409 image_id = kwargs.get('image_id', None)
411 foo(self, *args, **kwargs)
412 except ClientError as ce:
417 and 'image not found' in ('%s' % ce).lower())
420 msg = 'No image with id %s found' % image_id
421 raiseCLIError(ce, msg, details=this.about_image_id)
426 def metadata(this, foo):
427 def _raise(self, *args, **kwargs):
428 key = kwargs.get('key', None)
430 return foo(self, *args, **kwargs)
431 except ClientError as ce:
432 ce_msg = ('%s' % ce).lower()
433 if ce.status == 404 or (
434 ce.status == 400 and 'metadata' in ce_msg):
435 msg = 'No properties with key %s in this image' % key
436 raiseCLIError(ce, msg)
441 class pithos(object):
443 'To specify a container:',
444 ' 1. --container=<container> (temporary, overrides all)',
445 ' 2. Use the container:path format (temporary, overrides 3)',
446 ' 3. Set pithos_container variable (permanent)',
447 ' /config set pithos_container <container>',
448 'For a list of containers: /file list']
451 def connection(this, foo):
452 return generic._connection(foo)
455 def account(this, foo):
456 def _raise(self, *args, **kwargs):
458 return foo(self, *args, **kwargs)
459 except ClientError as ce:
463 'Invalid account credentials for this operation',
464 details=['Check user account settings'])
469 def quota(this, foo):
470 def _raise(self, *args, **kwargs):
472 return foo(self, *args, **kwargs)
473 except ClientError as ce:
475 raiseCLIError(ce, 'User quota exceeded', details=[
477 ' * upper total limit: /file quota',
478 ' * container limit:',
479 ' /file containerlimit get <container>',
480 '* set a higher container limit:',
481 ' /file containerlimit set <limit> <container>'])
486 def container(this, foo):
487 def _raise(self, *args, **kwargs):
488 dst_cont = kwargs.get('dst_cont', None)
490 return foo(self, *args, **kwargs)
491 except ClientError as ce:
492 if ce.status == 404 and 'container' in ('%s' % ce).lower():
493 cont = ('%s or %s' % (
495 dst_cont)) if dst_cont else self.container
496 msg = 'Is container %s in current account?' % (cont),
497 raiseCLIError(ce, msg, details=this.container_howto)
502 def local_path(this, foo):
503 def _raise(self, *args, **kwargs):
504 local_path = kwargs.get('local_path', '<None>')
506 return foo(self, *args, **kwargs)
507 except IOError as ioe:
508 msg = 'Failed to access file %s' % local_path,
509 raiseCLIError(ioe, msg, importance=2)
513 def object_path(this, foo):
514 def _raise(self, *args, **kwargs):
516 return foo(self, *args, **kwargs)
517 except ClientError as ce:
518 err_msg = ('%s' % ce).lower()
520 ce.status == 404 or ce.status == 500
521 ) and 'object' in err_msg and 'not' in err_msg:
522 msg = 'No object %s in container %s' % (
525 raiseCLIError(ce, msg, details=this.container_howto)
530 def object_size(this, foo):
531 def _raise(self, *args, **kwargs):
532 size = kwargs.get('size', None)
533 start = kwargs.get('start', 0)
534 end = kwargs.get('end', 0)
538 except ValueError as ve:
539 msg = 'Invalid file size %s ' % size
540 details = ['size must be a positive integer']
541 raiseCLIError(ve, msg, details=details, importance=1)
545 except ValueError as e:
546 msg = 'Invalid start value %s in range' % start,
547 details = ['size must be a positive integer'],
548 raiseCLIError(e, msg, details=details, importance=1)
551 except ValueError as e:
552 msg = 'Invalid end value %s in range' % end
553 details = ['size must be a positive integer']
554 raiseCLIError(e, msg, details=details, importance=1)
557 'Invalid range %s-%s' % (start, end),
558 details=['size must be a positive integer'],
562 return foo(self, *args, **kwargs)
563 except ClientError as ce:
564 err_msg = ('%s' % ce).lower()
565 expected = 'object length is smaller than range length'
567 ce.status == 416 or (
568 ce.status == 400 and expected in err_msg)):
569 raiseCLIError(ce, 'Remote object %s:%s <= %s %s' % (
570 self.container, self.path, format_size(size),
571 ('(%sB)' % size) if size >= 1024 else ''))