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>']
186 net_types = ('CUSTOM', 'MAC_FILTERED', 'IP_LESS_ROUTED', 'PHYSICAL_VLAN')
189 def connection(this, foo):
190 return generic._connection(foo)
194 def _raise(self, *args, **kwargs):
196 return foo(self, *args, **kwargs)
197 except ClientError as ce:
198 if ce.status == 400 and 'changes-since' in ('%s' % ce):
200 'Incorrect date format for --since',
201 details=['Accepted date format: d/m/y'])
206 def cluster_size(this, foo):
207 def _raise(self, *args, **kwargs):
208 size = kwargs.get('size', None)
211 assert size > 0, 'Cluster size must be a positive integer'
212 return foo(self, *args, **kwargs)
213 except ValueError as ve:
214 msg = 'Invalid cluster size value %s' % size
215 raiseCLIError(ve, msg, importance=1, details=[
216 'Cluster size must be a positive integer'])
217 except AssertionError as ae:
219 ae, 'Invalid cluster size %s' % size, importance=1)
225 def network_id(this, foo):
226 def _raise(self, *args, **kwargs):
227 network_id = kwargs.get('network_id', None)
229 network_id = int(network_id)
230 return foo(self, *args, **kwargs)
231 except ValueError as ve:
232 msg = 'Invalid network id %s ' % network_id
233 details = 'network id must be a positive integer'
234 raiseCLIError(ve, msg, details=details, importance=1)
235 except ClientError as ce:
236 if network_id and ce.status == 404 and (
237 'network' in ('%s' % ce).lower()
239 msg = 'No network with id %s found' % network_id,
240 raiseCLIError(ce, msg, details=this.about_network_id)
245 def network_type(this, foo):
246 def _raise(self, *args, **kwargs):
247 network_type = kwargs.get('network_type', None)
248 msg = 'Invalid network type %s.\nValid types: %s' % (
249 network_type, ' '.join(this.net_types))
250 assert network_type in this.net_types, msg
251 return foo(self, *args, **kwargs)
255 def network_max(this, foo):
256 def _raise(self, *args, **kwargs):
258 return foo(self, *args, **kwargs)
259 except ClientError as ce:
261 msg = 'Cannot create another network',
263 'Maximum number of networks reached',
264 '* to get a list of networks: /network list',
265 '* to delete a network: /network delete <net id>']
266 raiseCLIError(ce, msg, details=details)
271 def network_in_use(this, foo):
272 def _raise(self, *args, **kwargs):
273 network_id = kwargs.get('network_id', None)
275 return foo(self, *args, **kwargs)
276 except ClientError as ce:
277 if network_id and ce.status in (400, ):
278 msg = 'Network with id %s does not exist' % network_id,
279 raiseCLIError(ce, msg, details=this.about_network_id)
280 elif network_id or ce.status in (421, ):
281 msg = 'Network with id %s is in use' % network_id,
282 raiseCLIError(ce, msg, details=[
283 'Disconnect all nics/servers of this network first',
284 '* to get nics: /network info %s' % network_id,
285 '. (under "attachments" section)',
286 '* to disconnect: /network disconnect <nic id>'])
291 def flavor_id(this, foo):
292 def _raise(self, *args, **kwargs):
293 flavor_id = kwargs.get('flavor_id', None)
295 flavor_id = int(flavor_id)
296 return foo(self, *args, **kwargs)
297 except ValueError as ve:
298 msg = 'Invalid flavor id %s ' % flavor_id,
299 details = 'Flavor id must be a positive integer'
300 raiseCLIError(ve, msg, details=details, importance=1)
301 except ClientError as ce:
302 if flavor_id and ce.status == 404 and (
303 'flavor' in ('%s' % ce).lower()
305 msg = 'No flavor with id %s found' % flavor_id,
306 raiseCLIError(ce, msg, details=this.about_flavor_id)
311 def server_id(this, foo):
312 def _raise(self, *args, **kwargs):
313 server_id = kwargs.get('server_id', None)
315 server_id = int(server_id)
316 return foo(self, *args, **kwargs)
317 except ValueError as ve:
318 msg = 'Invalid virtual server id %s' % server_id,
319 details = 'Server id must be a positive integer'
320 raiseCLIError(ve, msg, details=details, importance=1)
321 except ClientError as ce:
322 err_msg = ('%s' % ce).lower()
324 ce.status == 404 and 'server' in err_msg
326 ce.status == 400 and 'not found' in err_msg
328 msg = 'virtual server with id %s not found' % server_id,
329 raiseCLIError(ce, msg, details=[
330 '* to get ids of all servers: /server list',
331 '* to get server details: /server info <server id>'])
336 def firewall(this, foo):
337 def _raise(self, *args, **kwargs):
338 profile = kwargs.get('profile', None)
340 return foo(self, *args, **kwargs)
341 except ClientError as ce:
342 if ce.status == 400 and profile and (
343 'firewall' in ('%s' % ce).lower()
345 msg = '%s is an invalid firewall profile term' % profile
346 raiseCLIError(ce, msg, details=[
347 'Try one of the following:',
348 '* DISABLED: Shutdown firewall',
349 '* ENABLED: Firewall in normal mode',
350 '* PROTECTED: Firewall in secure mode'])
355 def nic_id(this, foo):
356 def _raise(self, *args, **kwargs):
358 return foo(self, *args, **kwargs)
359 except ClientError as ce:
360 nic_id = kwargs.get('nic_id', None)
361 if nic_id and ce.status == 404 and (
362 'network interface' in ('%s' % ce).lower()
364 server_id = kwargs.get('server_id', '<no server>')
365 err_msg = 'No nic %s on virtual server with id %s' % (
368 raiseCLIError(ce, err_msg, details=[
369 '* check v. server with id %s: /server info %s' % (
372 '* list nics for v. server with id %s:' % server_id,
373 ' /server addr %s' % server_id])
378 def nic_format(this, foo):
379 def _raise(self, *args, **kwargs):
381 return foo(self, *args, **kwargs)
382 except IndexError as ie:
383 nic_id = kwargs.get('nic_id', None)
384 msg = 'Invalid format for network interface (nic) %s' % nic_id
385 raiseCLIError(ie, msg, importance=1, details=[
386 'nid_id format: nic-<server id>-<nic id>',
387 '* get nics of a network: /network info <net id>',
388 ' (listed the "attachments" section)'])
392 def metadata(this, foo):
393 def _raise(self, *args, **kwargs):
394 key = kwargs.get('key', None)
396 foo(self, *args, **kwargs)
397 except ClientError as ce:
398 if key and ce.status == 404 and (
399 'metadata' in ('%s' % ce).lower()
402 ce, 'No virtual server metadata with key %s' % key)
407 class plankton(object):
410 'How to pick a suitable image:',
411 '* get a list of image ids: /image list',
412 '* details of image: /image meta <image id>']
415 def connection(this, foo):
416 return generic._connection(foo)
420 def _raise(self, *args, **kwargs):
421 image_id = kwargs.get('image_id', None)
423 foo(self, *args, **kwargs)
424 except ClientError as ce:
429 and 'image not found' in ('%s' % ce).lower())
432 msg = 'No image with id %s found' % image_id
433 raiseCLIError(ce, msg, details=this.about_image_id)
438 def metadata(this, foo):
439 def _raise(self, *args, **kwargs):
440 key = kwargs.get('key', None)
442 return foo(self, *args, **kwargs)
443 except ClientError as ce:
444 ce_msg = ('%s' % ce).lower()
445 if ce.status == 404 or (
446 ce.status == 400 and 'metadata' in ce_msg):
447 msg = 'No properties with key %s in this image' % key
448 raiseCLIError(ce, msg)
453 class pithos(object):
455 'To specify a container:',
456 ' 1. --container=<container> (temporary, overrides all)',
457 ' 2. Use the container:path format (temporary, overrides 3)',
458 ' 3. Set pithos_container variable (permanent)',
459 ' /config set pithos_container <container>',
460 'For a list of containers: /file list']
463 def connection(this, foo):
464 return generic._connection(foo)
467 def account(this, foo):
468 def _raise(self, *args, **kwargs):
470 return foo(self, *args, **kwargs)
471 except ClientError as ce:
475 'Invalid account credentials for this operation',
476 details=['Check user account settings'])
481 def quota(this, foo):
482 def _raise(self, *args, **kwargs):
484 return foo(self, *args, **kwargs)
485 except ClientError as ce:
487 raiseCLIError(ce, 'User quota exceeded', details=[
489 ' * upper total limit: /file quota',
490 ' * container limit:',
491 ' /file containerlimit get <container>',
492 '* set a higher container limit:',
493 ' /file containerlimit set <limit> <container>'])
498 def container(this, foo):
499 def _raise(self, *args, **kwargs):
500 dst_cont = kwargs.get('dst_cont', None)
502 return foo(self, *args, **kwargs)
503 except ClientError as ce:
504 if ce.status == 404 and 'container' in ('%s' % ce).lower():
505 cont = ('%s or %s' % (
507 dst_cont)) if dst_cont else self.container
508 msg = 'Is container %s in current account?' % (cont),
509 raiseCLIError(ce, msg, details=this.container_howto)
514 def local_path_download(this, foo):
515 def _raise(self, *args, **kwargs):
517 return foo(self, *args, **kwargs)
518 except IOError as ioe:
519 msg = 'Failed to access a file',
520 raiseCLIError(ioe, msg, importance=2, details=[
521 'Check if the file exists. Also check if the remote',
522 'directories exist. All directories in a remote path',
523 'must exist to succesfully download a container or a',
525 'To create a remote directory:',
526 ' [kamaki] file mkdir REMOTE_DIRECTORY_PATH'])
530 def local_path(this, foo):
531 def _raise(self, *args, **kwargs):
532 local_path = kwargs.get('local_path', None)
534 return foo(self, *args, **kwargs)
535 except IOError as ioe:
536 msg = 'Failed to access file %s' % local_path,
537 raiseCLIError(ioe, msg, importance=2)
541 def object_path(this, foo):
542 def _raise(self, *args, **kwargs):
544 return foo(self, *args, **kwargs)
545 except ClientError as ce:
546 err_msg = ('%s' % ce).lower()
548 ce.status == 404 or ce.status == 500
549 ) and 'object' in err_msg and 'not' in err_msg:
550 msg = 'No object %s in container %s' % (
553 raiseCLIError(ce, msg, details=this.container_howto)
558 def object_size(this, foo):
559 def _raise(self, *args, **kwargs):
560 size = kwargs.get('size', None)
561 start = kwargs.get('start', 0)
562 end = kwargs.get('end', 0)
566 except ValueError as ve:
567 msg = 'Invalid file size %s ' % size
568 details = ['size must be a positive integer']
569 raiseCLIError(ve, msg, details=details, importance=1)
573 except ValueError as e:
574 msg = 'Invalid start value %s in range' % start,
575 details = ['size must be a positive integer'],
576 raiseCLIError(e, msg, details=details, importance=1)
579 except ValueError as e:
580 msg = 'Invalid end value %s in range' % end
581 details = ['size must be a positive integer']
582 raiseCLIError(e, msg, details=details, importance=1)
585 'Invalid range %s-%s' % (start, end),
586 details=['size must be a positive integer'],
590 return foo(self, *args, **kwargs)
591 except ClientError as ce:
592 err_msg = ('%s' % ce).lower()
593 expected = 'object length is smaller than range length'
595 ce.status == 416 or (
596 ce.status == 400 and expected in err_msg)):
597 raiseCLIError(ce, 'Remote object %s:%s <= %s %s' % (
598 self.container, self.path, format_size(size),
599 ('(%sB)' % size) if size >= 1024 else ''))