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
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
42 sendlog = logging.getLogger('clients.send')
43 datasendlog = logging.getLogger('data.send')
44 recvlog = logging.getLogger('clients.recv')
45 datarecvlog = logging.getLogger('data.recv')
48 class generic(object):
52 def _raise(self, *args, **kwargs):
54 return foo(self, *args, **kwargs)
55 except Exception as e:
63 def _connection(this, foo, base_url):
64 def _raise(self, *args, **kwargs):
66 foo(self, *args, **kwargs)
67 except ClientError as ce:
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)
84 url = getattr(client, 'base_url', '<empty>')
86 'Invalid service url %s' % url,
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>'])
95 class astakos(object):
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']
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: kamaki config set token <tkn>)')
114 if not getattr(client, 'base_url', False):
115 raise CLIError('Missing astakos server URL',
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>'])
124 def authenticate(this, foo):
125 def _raise(self, *args, **kwargs):
127 r = foo(self, *args, **kwargs)
128 except ClientError as ce:
130 token = kwargs.get('custom_token', 0) or self.client.token
132 'Authorization failed for token %s' % token if token\
133 else 'No token provided',
134 details=[] if token else this._token_details)
140 class history(object):
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)
151 def _get_cmd_ids(this, foo):
152 def _raise(self, cmd_ids, *args, **kwargs):
154 raise CLISyntaxError('Usage: <id1|id1-id2> [id3|id3-id4] ...',
155 details=self.__doc__.split('\n'))
156 return foo(self, cmd_ids, *args, **kwargs)
160 class cyclades(object):
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>']
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>']
172 def connection(this, foo):
173 return generic._connection(foo, 'compute.url')
177 def _raise(self, *args, **kwargs):
179 return foo(self, *args, **kwargs)
180 except ClientError as ce:
181 if ce.status == 400 and 'changes-since' in ('%s' % ce):
183 'Incorrect date format for --since',
184 details=['Accepted date format: d/m/y'])
189 def network_id(this, foo):
190 def _raise(self, *args, **kwargs):
191 network_id = kwargs.get('network_id', None)
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',
199 except ClientError as ce:
200 if network_id and ce.status == 404 and\
201 'network' in ('%s' % ce).lower():
203 'No network with id %s found' % network_id,
204 details=this.about_network_id)
209 def network_max(this, foo):
210 def _raise(self, *args, **kwargs):
212 return foo(self, *args, **kwargs)
213 except ClientError as 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>'])
224 def network_in_use(this, foo):
225 def _raise(self, *args, **kwargs):
226 network_id = kwargs.get('network_id', None)
228 return foo(self, *args, **kwargs)
229 except ClientError as ce:
230 if network_id and ce.status == 400:
232 'Network with id %s does not exist' % network_id,
233 details=self.about_network_id)
234 elif network_id or ce.status == 421:
236 'Network with id %s is in use' % network_id,
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>'])
246 def flavor_id(this, foo):
247 def _raise(self, *args, **kwargs):
248 flavor_id = kwargs.get('flavor_id', None)
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',
256 except ClientError as ce:
257 if flavor_id and ce.status == 404 and\
258 'flavor' in ('%s' % ce).lower():
260 'No flavor with id %s found' % flavor_id,
261 details=this.about_flavor_id)
266 def server_id(this, foo):
267 def _raise(self, *args, **kwargs):
268 server_id = kwargs.get('server_id', None)
270 server_id = int(server_id)
271 return foo(self, *args, **kwargs)
272 except ValueError as ve:
274 'Invalid server(VM) id %s' % server_id,
275 details=['id must be a positive integer'],
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):
282 'server(VM) with id %s not found' % server_id,
284 '* to get existing VM ids: /server list',
285 '* to get VM details: /server info <VM id>'])
290 def firewall(this, foo):
291 def _raise(self, *args, **kwargs):
292 profile = kwargs.get('profile', None)
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():
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'])
308 def nic_id(this, foo):
309 def _raise(self, *args, **kwargs):
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' % (
320 raiseCLIError(ce, err_msg, details=[
321 '* check server(VM) with id %s: /server info %s' % (
324 '* list nics for server(VM) with id %s:' % server_id,
325 ' /server addr %s' % server_id])
330 def nic_format(this, foo):
331 def _raise(self, *args, **kwargs):
333 return foo(self, *args, **kwargs)
334 except IndexError as ie:
335 nic_id = kwargs.get('nic_id', None)
337 'Invalid format for network interface (nic) %s' % nic_id,
340 'nid_id format: nic-<server id>-<nic id>',
341 '* get nics of a network: /network info <net id>',
342 ' (listed the "attachments" section)'])
346 def metadata(this, foo):
347 def _raise(self, *args, **kwargs):
348 key = kwargs.get('key', None)
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)
359 class plankton(object):
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>']
366 def connection(this, foo):
367 return generic._connection(foo, 'image.url')
371 def _raise(self, *args, **kwargs):
372 image_id = kwargs.get('image_id', None)
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):
381 'No image with id %s found' % image_id,
382 details=this.about_image_id)
387 def metadata(this, foo):
388 def _raise(self, *args, **kwargs):
389 key = kwargs.get('key', None)
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())):
396 'No properties with key %s in this image' % key)
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']
410 def connection(this, foo):
411 return generic._connection(foo, 'store.url')
414 def quota(this, foo):
415 def _raise(self, *args, **kwargs):
417 return foo(self, *args, **kwargs)
418 except ClientError as ce:
420 raiseCLIError(ce, 'User quota exceeded', details=[
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>'])
431 def container(this, foo):
432 def _raise(self, *args, **kwargs):
433 dst_cont = kwargs.get('dst_cont', None)
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
441 'Is container %s in account %s ?' % (
444 details=this.container_howto)
449 def local_path(this, foo):
450 def _raise(self, *args, **kwargs):
451 local_path = kwargs.get('local_path', '<None>')
453 return foo(self, *args, **kwargs)
454 except IOError as ioe:
456 'Failed to access file %s' % local_path,
461 def object_path(this, foo):
462 def _raise(self, *args, **kwargs):
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:
470 'No object %s in %s\'s container %s'\
471 % (self.path, self.account, self.container),
472 details=this.container_howto)
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)
485 except ValueError as ve:
487 'Invalid file size %s ' % size,
488 details=['size must be a positive integer'],
493 except ValueError as e:
495 'Invalid start value %s in range' % start,
496 details=['size must be a positive integer'],
500 except ValueError as e:
502 'Invalid end value %s in range' % end,
503 details=['size must be a positive integer'],
507 'Invalid range %s-%s' % (start, end),
508 details=['size must be a positive integer'],
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)):
519 'Remote object %s:%s <= %s %s' % (
523 ('(%sB)' % size) if size >= 1024 else ''))