1 # Copyright 2011-2012 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
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
42 'Note: If you use a named cloud, use its name instead of "default"']
45 class generic(object):
49 def _raise(self, *args, **kwargs):
51 return foo(self, *args, **kwargs)
52 except Exception as e:
56 if isinstance(e, CLIError) or isinstance(e, ClientError):
58 raiseCLIError(e, details=['%s, -d for debug info' % type(e)])
62 def _connection(this, foo):
63 def _raise(self, *args, **kwargs):
65 foo(self, *args, **kwargs)
66 except ClientError as ce:
67 ce_msg = ('%s' % ce).lower()
69 raiseCLIError(ce, 'Authorization failed', details=[
70 'Make sure a valid token is provided:',
71 ' to check if token is valid: /user authenticate',
73 ' /config set cloud.default.token <token>',
74 ' to get current token:',
75 ' /config get cloud.default.token'] + CLOUDNAME)
76 elif ce.status in range(-12, 200) + [302, 401, 403, 500]:
77 raiseCLIError(ce, importance=3, details=[
78 'Check if service is up'])
79 elif ce.status == 404 and 'kamakihttpresponse' in ce_msg:
80 client = getattr(self, 'client', None)
83 url = getattr(client, 'base_url', '<empty>')
84 msg = 'Invalid service url %s' % url
85 raiseCLIError(ce, msg, details=[
86 'Check if authentication url is correct',
87 ' check current url:',
88 ' /config get cloud.default.url',
89 ' set new auth. url:',
90 ' /config set cloud.default.url'] + CLOUDNAME)
98 'To check default token: /config get cloud.default.token',
99 'If set/update a token:',
100 '* (permanent): /config set cloud.default.token <token>',
101 '* (temporary): re-run with <token> parameter'] + CLOUDNAME
105 def _raise(self, *args, **kwargs):
106 r = foo(self, *args, **kwargs)
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):
113 'No permanent token (try:'
114 ' kamaki config set cloud.default.token <tkn>)')
115 if not getattr(client, 'base_url', False):
116 msg = 'Missing synnefo authentication URL'
117 raise CLIError(msg, importance=3, details=[
118 'Check if authentication url is correct',
119 ' check current url:',
120 ' /config get cloud.default.url',
121 ' set new auth. url:',
122 ' /config set cloud.default.url'] + CLOUDNAME)
127 def authenticate(this, foo):
128 def _raise(self, *args, **kwargs):
130 return foo(self, *args, **kwargs)
131 except ClientError as ce:
133 token = kwargs.get('custom_token', 0) or self.client.token
135 'Authorization failed for token %s' % token
136 ) if token else 'No token provided',
137 details = [] if token else this._token_details
138 raiseCLIError(ce, msg, details=details)
144 class history(object):
147 def _raise(self, *args, **kwargs):
148 r = foo(self, *args, **kwargs)
149 if not hasattr(self, 'history'):
150 raise CLIError('Failed to load history', importance=2)
155 def _get_cmd_ids(this, foo):
156 def _raise(self, cmd_ids, *args, **kwargs):
158 raise CLISyntaxError(
159 'Usage: <id1|id1-id2> [id3|id3-id4] ...',
160 details=self.__doc__.split('\n'))
161 return foo(self, cmd_ids, *args, **kwargs)
165 class cyclades(object):
167 'How to pick a valid flavor id:',
168 '* get a list of flavor ids: /flavor list',
169 '* details of flavor: /flavor info <flavor id>']
172 'How to pick a valid network id:',
173 '* get a list of network ids: /network list',
174 '* details of network: /network info <network id>']
177 def connection(this, foo):
178 return generic._connection(foo)
182 def _raise(self, *args, **kwargs):
184 return foo(self, *args, **kwargs)
185 except ClientError as ce:
186 if ce.status == 400 and 'changes-since' in ('%s' % ce):
188 'Incorrect date format for --since',
189 details=['Accepted date format: d/m/y'])
194 def network_id(this, foo):
195 def _raise(self, *args, **kwargs):
196 network_id = kwargs.get('network_id', None)
198 network_id = int(network_id)
199 return foo(self, *args, **kwargs)
200 except ValueError as ve:
201 msg = 'Invalid network id %s ' % network_id
202 details = ['network id must be a positive integer']
203 raiseCLIError(ve, msg, details=details, importance=1)
204 except ClientError as ce:
205 if network_id and ce.status == 404 and (
206 'network' in ('%s' % ce).lower()
208 msg = 'No network with id %s found' % network_id,
209 raiseCLIError(ce, msg, details=this.about_network_id)
214 def network_max(this, foo):
215 def _raise(self, *args, **kwargs):
217 return foo(self, *args, **kwargs)
218 except ClientError as ce:
220 msg = 'Cannot create another network',
222 'Maximum number of networks reached',
223 '* to get a list of networks: /network list',
224 '* to delete a network: /network delete <net id>']
225 raiseCLIError(ce, msg, details=details)
230 def network_in_use(this, foo):
231 def _raise(self, *args, **kwargs):
232 network_id = kwargs.get('network_id', None)
234 return foo(self, *args, **kwargs)
235 except ClientError as ce:
236 if network_id and ce.status == 400:
237 msg = 'Network with id %s does not exist' % network_id,
238 raiseCLIError(ce, msg, details=this.about_network_id)
239 elif network_id or ce.status == 421:
240 msg = 'Network with id %s is in use' % network_id,
241 raiseCLIError(ce, msg, details=[
242 'Disconnect all nics/VMs of this network first',
243 '* to get nics: /network info %s' % network_id,
244 '. (under "attachments" section)',
245 '* to disconnect: /network disconnect <nic id>'])
250 def flavor_id(this, foo):
251 def _raise(self, *args, **kwargs):
252 flavor_id = kwargs.get('flavor_id', None)
254 flavor_id = int(flavor_id)
255 return foo(self, *args, **kwargs)
256 except ValueError as ve:
257 msg = 'Invalid flavor id %s ' % flavor_id,
258 details = 'Flavor id must be a positive integer',
259 raiseCLIError(ve, msg, details=details, importance=1)
260 except ClientError as ce:
261 if flavor_id and ce.status == 404 and (
262 'flavor' in ('%s' % ce).lower()
264 msg = 'No flavor with id %s found' % flavor_id,
265 raiseCLIError(ce, msg, details=this.about_flavor_id)
270 def server_id(this, foo):
271 def _raise(self, *args, **kwargs):
272 server_id = kwargs.get('server_id', None)
274 server_id = int(server_id)
275 return foo(self, *args, **kwargs)
276 except ValueError as ve:
277 msg = 'Invalid server(VM) id %s' % server_id,
278 details = ['id must be a positive integer'],
279 raiseCLIError(ve, msg, details=details, importance=1)
280 except ClientError as ce:
281 err_msg = ('%s' % ce).lower()
283 ce.status == 404 and 'server' in err_msg
285 ce.status == 400 and 'not found' in err_msg
287 msg = 'server(VM) with id %s not found' % server_id,
288 raiseCLIError(ce, msg, details=[
289 '* to get existing VM ids: /server list',
290 '* to get VM details: /server info <VM id>'])
295 def firewall(this, foo):
296 def _raise(self, *args, **kwargs):
297 profile = kwargs.get('profile', None)
299 return foo(self, *args, **kwargs)
300 except ClientError as ce:
301 if ce.status == 400 and profile and (
302 'firewall' in ('%s' % ce).lower()
304 msg = '%s is an invalid firewall profile term' % profile
305 raiseCLIError(ce, msg, details=[
306 'Try one of the following:',
307 '* DISABLED: Shutdown firewall',
308 '* ENABLED: Firewall in normal mode',
309 '* PROTECTED: Firewall in secure mode'])
314 def nic_id(this, foo):
315 def _raise(self, *args, **kwargs):
317 return foo(self, *args, **kwargs)
318 except ClientError as ce:
319 nic_id = kwargs.get('nic_id', None)
320 if nic_id and ce.status == 404 and (
321 'network interface' in ('%s' % ce).lower()
323 server_id = kwargs.get('server_id', '<no server>')
324 err_msg = 'No nic %s on server(VM) with id %s' % (
327 raiseCLIError(ce, err_msg, details=[
328 '* check server(VM) with id %s: /server info %s' % (
331 '* list nics for server(VM) with id %s:' % server_id,
332 ' /server addr %s' % server_id])
337 def nic_format(this, foo):
338 def _raise(self, *args, **kwargs):
340 return foo(self, *args, **kwargs)
341 except IndexError as ie:
342 nic_id = kwargs.get('nic_id', None)
343 msg = 'Invalid format for network interface (nic) %s' % nic_id
344 raiseCLIError(ie, msg, importance=1, details=[
345 'nid_id format: nic-<server id>-<nic id>',
346 '* get nics of a network: /network info <net id>',
347 ' (listed the "attachments" section)'])
351 def metadata(this, foo):
352 def _raise(self, *args, **kwargs):
353 key = kwargs.get('key', None)
355 foo(self, *args, **kwargs)
356 except ClientError as ce:
357 if key and ce.status == 404 and (
358 'metadata' in ('%s' % ce).lower()
360 raiseCLIError(ce, 'No VM metadata with key %s' % key)
365 class plankton(object):
368 'How to pick a suitable image:',
369 '* get a list of image ids: /image list',
370 '* details of image: /flavor info <image id>']
373 def connection(this, foo):
374 return generic._connection(foo)
378 def _raise(self, *args, **kwargs):
379 image_id = kwargs.get('image_id', None)
381 foo(self, *args, **kwargs)
382 except ClientError as ce:
387 and 'image not found' in ('%s' % ce).lower())
390 msg = 'No image with id %s found' % image_id
391 raiseCLIError(ce, msg, details=this.about_image_id)
396 def metadata(this, foo):
397 def _raise(self, *args, **kwargs):
398 key = kwargs.get('key', None)
400 return foo(self, *args, **kwargs)
401 except ClientError as ce:
402 ce_msg = ('%s' % ce).lower()
403 if ce.status == 404 or (
404 ce.status == 400 and 'metadata' in ce_msg):
405 msg = 'No properties with key %s in this image' % key
406 raiseCLIError(ce, msg)
411 class pithos(object):
413 'To specify a container:',
414 ' 1. --container=<container> (temporary, overrides all)',
415 ' 2. Use the container:path format (temporary, overrides 3)',
416 ' 3. Set pithos_container variable (permanent)',
417 ' /config set pithos_container <container>',
418 'For a list of containers: /file list']
421 def connection(this, foo):
422 return generic._connection(foo)
425 def account(this, foo):
426 def _raise(self, *args, **kwargs):
428 return foo(self, *args, **kwargs)
429 except ClientError as ce:
433 'Invalid account credentials for this operation',
434 details=['Check user account settings'])
439 def quota(this, foo):
440 def _raise(self, *args, **kwargs):
442 return foo(self, *args, **kwargs)
443 except ClientError as ce:
445 raiseCLIError(ce, 'User quota exceeded', details=[
447 ' * upper total limit: /file quota',
448 ' * container limit:',
449 ' /file containerlimit get <container>',
450 '* set a higher container limit:',
451 ' /file containerlimit set <limit> <container>'])
456 def container(this, foo):
457 def _raise(self, *args, **kwargs):
458 dst_cont = kwargs.get('dst_cont', None)
460 return foo(self, *args, **kwargs)
461 except ClientError as ce:
462 if ce.status == 404 and 'container' in ('%s' % ce).lower():
463 cont = ('%s or %s' % (
465 dst_cont)) if dst_cont else self.container
466 msg = 'Is container %s in current account?' % (cont),
467 raiseCLIError(ce, msg, details=this.container_howto)
472 def local_path(this, foo):
473 def _raise(self, *args, **kwargs):
474 local_path = kwargs.get('local_path', '<None>')
476 return foo(self, *args, **kwargs)
477 except IOError as ioe:
478 msg = 'Failed to access file %s' % local_path,
479 raiseCLIError(ioe, msg, importance=2)
483 def object_path(this, foo):
484 def _raise(self, *args, **kwargs):
486 return foo(self, *args, **kwargs)
487 except ClientError as ce:
488 err_msg = ('%s' % ce).lower()
490 ce.status == 404 or ce.status == 500
491 ) and 'object' in err_msg and 'not' in err_msg:
492 msg = 'No object %s in container %s' % (
495 raiseCLIError(ce, msg, details=this.container_howto)
500 def object_size(this, foo):
501 def _raise(self, *args, **kwargs):
502 size = kwargs.get('size', None)
503 start = kwargs.get('start', 0)
504 end = kwargs.get('end', 0)
508 except ValueError as ve:
509 msg = 'Invalid file size %s ' % size
510 details = ['size must be a positive integer']
511 raiseCLIError(ve, msg, details=details, importance=1)
515 except ValueError as e:
516 msg = 'Invalid start value %s in range' % start,
517 details = ['size must be a positive integer'],
518 raiseCLIError(e, msg, details=details, importance=1)
521 except ValueError as e:
522 msg = 'Invalid end value %s in range' % end
523 details = ['size must be a positive integer']
524 raiseCLIError(e, msg, details=details, importance=1)
527 'Invalid range %s-%s' % (start, end),
528 details=['size must be a positive integer'],
532 return foo(self, *args, **kwargs)
533 except ClientError as ce:
534 err_msg = ('%s' % ce).lower()
535 expected = 'object length is smaller than range length'
537 ce.status == 416 or (
538 ce.status == 400 and expected in err_msg)):
539 raiseCLIError(ce, 'Remote object %s:%s <= %s %s' % (
540 self.container, self.path, format_size(size),
541 ('(%sB)' % size) if size >= 1024 else ''))