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:
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: /astakos 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)
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>'])
94 class astakos(object):
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']
104 def _raise(self, *args, **kwargs):
105 r = foo(self, *args, **kwargs)
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):
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 astakos.url is set correctly',
117 'To get astakos url: /config get astakos.url',
118 'To set astakos url: /config set astakos.url <URL>'])
123 def authenticate(this, foo):
124 def _raise(self, *args, **kwargs):
126 return foo(self, *args, **kwargs)
127 except ClientError as ce:
129 token = kwargs.get('custom_token', 0) or self.client.token
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)
139 class history(object):
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)
150 def _get_cmd_ids(this, foo):
151 def _raise(self, cmd_ids, *args, **kwargs):
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)
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 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()
203 msg = 'No network with id %s found' % network_id,
204 raiseCLIError(ce, msg, 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:
215 msg = 'Cannot create another network',
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)
225 def network_in_use(this, foo):
226 def _raise(self, *args, **kwargs):
227 network_id = kwargs.get('network_id', None)
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=self.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>'])
245 def flavor_id(this, foo):
246 def _raise(self, *args, **kwargs):
247 flavor_id = kwargs.get('flavor_id', None)
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()
259 msg = 'No flavor with id %s found' % flavor_id,
260 raiseCLIError(ce, msg, details=this.about_flavor_id)
265 def server_id(this, foo):
266 def _raise(self, *args, **kwargs):
267 server_id = kwargs.get('server_id', None)
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()
278 ce.status == 404 and 'server' in err_msg
280 ce.status == 400 and 'not found' in err_msg
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>'])
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 and (
297 'firewall' in ('%s' % ce).lower()
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'])
309 def nic_id(this, foo):
310 def _raise(self, *args, **kwargs):
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()
318 server_id = kwargs.get('server_id', '<no server>')
319 err_msg = 'No nic %s on server(VM) with id %s' % (
322 raiseCLIError(ce, err_msg, details=[
323 '* check server(VM) with id %s: /server info %s' % (
326 '* list nics for server(VM) with id %s:' % server_id,
327 ' /server addr %s' % server_id])
332 def nic_format(this, foo):
333 def _raise(self, *args, **kwargs):
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)'])
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 and (
353 'metadata' in ('%s' % ce).lower()
355 raiseCLIError(ce, 'No VM metadata with key %s' % key)
360 class plankton(object):
363 'How to pick a suitable image:',
364 '* get a list of image ids: /image list',
365 '* details of image: /flavor info <image id>']
368 def connection(this, foo):
369 return generic._connection(foo, 'image.url')
373 def _raise(self, *args, **kwargs):
374 image_id = kwargs.get('image_id', None)
376 foo(self, *args, **kwargs)
377 except ClientError as ce:
382 and 'image not found' in ('%s' % ce).lower())
385 msg = 'No image with id %s found' % image_id
386 raiseCLIError(ce, msg, details=this.about_image_id)
391 def metadata(this, foo):
392 def _raise(self, *args, **kwargs):
393 key = kwargs.get('key', None)
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)
406 class pithos(object):
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']
416 def connection(this, foo):
417 return generic._connection(foo, 'store.url')
420 def quota(this, foo):
421 def _raise(self, *args, **kwargs):
423 return foo(self, *args, **kwargs)
424 except ClientError as ce:
426 raiseCLIError(ce, 'User quota exceeded', details=[
428 ' * upper total limit: /store quota',
429 ' * container limit: /store quota <container>',
430 '* set a higher quota (if permitted):',
431 ' /store setquota <quota>[unit] <container>'
432 ' as long as <container quota> <= <total quota>'])
437 def container(this, foo):
438 def _raise(self, *args, **kwargs):
439 dst_cont = kwargs.get('dst_cont', None)
441 return foo(self, *args, **kwargs)
442 except ClientError as ce:
443 if ce.status == 404 and 'container' in ('%s' % ce).lower():
444 cont = ('%s or %s' % (
446 dst_cont)) if dst_cont else self.container
447 msg = 'Is container %s in current account?' % (cont),
448 raiseCLIError(ce, msg, details=this.container_howto)
453 def local_path(this, foo):
454 def _raise(self, *args, **kwargs):
455 local_path = kwargs.get('local_path', '<None>')
457 return foo(self, *args, **kwargs)
458 except IOError as ioe:
459 msg = 'Failed to access file %s' % local_path,
460 raiseCLIError(ioe, msg, importance=2)
464 def object_path(this, foo):
465 def _raise(self, *args, **kwargs):
467 return foo(self, *args, **kwargs)
468 except ClientError as ce:
469 err_msg = ('%s' % ce).lower()
471 ce.status == 404 or ce.status == 500
472 ) and 'object' in err_msg and 'not' in err_msg:
473 msg = 'No object %s in container %s' % (
476 raiseCLIError(ce, msg, details=this.container_howto)
481 def object_size(this, foo):
482 def _raise(self, *args, **kwargs):
483 size = kwargs.get('size', None)
484 start = kwargs.get('start', 0)
485 end = kwargs.get('end', 0)
489 except ValueError as ve:
490 msg = 'Invalid file size %s ' % size
491 details = ['size must be a positive integer']
492 raiseCLIError(ve, msg, details=details, importance=1)
496 except ValueError as e:
497 msg = 'Invalid start value %s in range' % start,
498 details = ['size must be a positive integer'],
499 raiseCLIError(e, msg, details=details, importance=1)
502 except ValueError as e:
503 msg = 'Invalid end value %s in range' % end
504 details = ['size must be a positive integer']
505 raiseCLIError(e, msg, details=details, importance=1)
508 'Invalid range %s-%s' % (start, end),
509 details=['size must be a positive integer'],
513 return foo(self, *args, **kwargs)
514 except ClientError as ce:
515 err_msg = ('%s' % ce).lower()
516 expected = 'object length is smaller than range length'
518 ce.status == 416 or (
519 ce.status == 400 and expected in err_msg)):
520 raiseCLIError(ce, 'Remote object %s:%s <= %s %s' % (
521 self.container, self.path, format_size(size),
522 ('(%sB)' % size) if size >= 1024 else ''))