from optparse import OptionParser
from pwd import getpwuid
-from kamaki.client import ComputeClient, ImagesClient, ClientError
+from kamaki.client import ComputeClient, GlanceClient, ClientError
from kamaki.config import Config, ConfigError
from kamaki.utils import OrderedDict, print_addresses, print_dict, print_items
# The defaults also determine the allowed keys
CONFIG_DEFAULTS = {
- 'apis': 'nova synnefo glance plankton',
+ 'apis': 'nova synnefo glance',
'token': '',
'compute_url': 'https://okeanos.grnet.gr/api/v1',
'images_url': 'https://okeanos.grnet.gr/plankton',
def main(self):
servers = self.client.list_servers(self.options.detail)
- print_items(servers, self.options.detail)
+ print_items(servers)
@command(api='nova')
def main(self):
flavors = self.client.list_flavors(self.options.detail)
- print_items(flavors, self.options.detail)
+ print_items(flavors)
@command(api='nova')
def main(self):
images = self.client.list_images(self.options.detail)
- print_items(images, self.options.detail)
+ print_items(images)
@command(api='nova')
def main(self):
networks = self.client.list_networks(self.options.detail)
- print_items(networks, self.options.detail)
+ print_items(networks)
@command(api='synnefo')
class glance_list(object):
"""list images"""
+ @classmethod
+ def update_parser(cls, parser):
+ parser.add_option('-l', dest='detail', action='store_true',
+ default=False, help='show detailed output')
+ parser.add_option('--container-format', dest='container_format',
+ metavar='FORMAT', help='filter by container format')
+ parser.add_option('--disk-format', dest='disk_format',
+ metavar='FORMAT', help='filter by disk format')
+ parser.add_option('--name', dest='name', metavar='NAME',
+ help='filter by name')
+ parser.add_option('--size-min', dest='size_min', metavar='BYTES',
+ help='filter by minimum size')
+ parser.add_option('--size-max', dest='size_max', metavar='BYTES',
+ help='filter by maximum size')
+ parser.add_option('--status', dest='status', metavar='STATUS',
+ help='filter by status')
+ parser.add_option('--order', dest='order', metavar='FIELD',
+ help='order by FIELD (use a - prefix to reverse order)')
+
def main(self):
- images = self.client.list_public()
- print images
+ filters = {}
+ for filter in ('container_format', 'disk_format', 'name', 'size_min',
+ 'size_max', 'status'):
+ val = getattr(self.options, filter, None)
+ if val is not None:
+ filters[filter] = val
+
+ order = self.options.order or ''
+ images = self.client.list_public(self.options.detail, filters=filters,
+ order=order)
+ print_items(images, title=('name',))
+
+
+@command(api='glance')
+class glance_register(object):
+ """register an image"""
+
+ @classmethod
+ def update_parser(cls, parser):
+ parser.add_option('--checksum', dest='checksum', metavar='CHECKSUM',
+ help='set image checksum')
+ parser.add_option('--container-format', dest='container_format',
+ metavar='FORMAT', help='set container format')
+ parser.add_option('--disk-format', dest='disk_format',
+ metavar='FORMAT', help='set disk format')
+ parser.add_option('--id', dest='id',
+ metavar='ID', help='set image ID')
+ parser.add_option('--owner', dest='owner',
+ metavar='USER', help='set image owner (admin only)')
+ parser.add_option('--property', dest='properties', action='append',
+ metavar='KEY=VAL',
+ help='add a property (can be used multiple times)')
+ parser.add_option('--public', dest='is_public', action='store_true',
+ help='mark image as public')
+ parser.add_option('--size', dest='size', metavar='SIZE',
+ help='set image size')
+
+ def main(self, name, location):
+ params = {}
+ for key in ('checksum', 'container_format', 'disk_format', 'id',
+ 'owner', 'is_public', 'size'):
+ val = getattr(self.options, key)
+ if val is not None:
+ params[key] = val
+
+ properties = {}
+ for property in self.options.properties or []:
+ key, sep, val = property.partition('=')
+ if not sep:
+ log.error("Invalid property '%s'", property)
+ return 1
+ properties[key.strip()] = val.strip()
+
+ self.client.register(name, location, params, properties)
def print_groups(groups):
parser.usage = '%prog <group> <command> [options]'
parser.add_option('--help', dest='help', action='store_true',
default=False, help='show this help message and exit')
- parser.add_option('--api', dest='apis', metavar='API', action='append',
+ parser.add_option('--api', dest='apis', action='append', metavar='API',
help='API to use (can be used multiple times)')
parser.add_option('--compute-url', dest='compute_url', metavar='URL',
help='URL for the compute API')
url = config.get('compute_url')
token = config.get('token')
cmd.client = ComputeClient(url, token)
- elif cmd.api in ('glance', 'plankton'):
+ elif cmd.api == 'glance':
url = config.get('images_url')
token = config.get('token')
- cmd.client = ImagesClient(url, token)
+ cmd.client = GlanceClient(url, token)
try:
return cmd.main(*args[3:])
- except TypeError:
- parser.print_help()
- return 1
+ except TypeError as e:
+ if e.args and e.args[0].startswith('main()'):
+ parser.print_help()
+ return 1
+ else:
+ raise
except ClientError, err:
log.error('%s', err.message)
log.info('%s', err.details)
import logging
from httplib import HTTPConnection, HTTPSConnection
+from urllib import quote
from urlparse import urlparse
self.url = url
self.token = token
- def _cmd(self, method, path, body=None, success=200):
+ def http_cmd(self, method, path, body=None, headers=None, success=200):
p = urlparse(self.url)
path = p.path + path
if p.scheme == 'http':
else:
raise ClientError('Unknown URL scheme')
- headers = {'X-Auth-Token': self.token}
+ headers = headers or {}
+ headers['X-Auth-Token'] = self.token
if body:
headers['Content-Type'] = 'application/json'
headers['Content-Length'] = len(body)
return reply
- def _get(self, path, success=200):
- return self._cmd('GET', path, None, success)
+ def http_get(self, path, success=200):
+ return self.http_cmd('GET', path, success=success)
- def _post(self, path, body, success=202):
- return self._cmd('POST', path, body, success)
+ def http_post(self, path, body=None, headers=None, success=202):
+ return self.http_cmd('POST', path, body, headers, success)
- def _put(self, path, body, success=204):
- return self._cmd('PUT', path, body, success)
+ def http_put(self, path, body, success=204):
+ return self.http_cmd('PUT', path, body, success=success)
- def _delete(self, path, success=204):
- return self._cmd('DELETE', path, None, success)
+ def http_delete(self, path, success=204):
+ return self.http_cmd('DELETE', path, success=success)
class ComputeClient(Client):
def list_servers(self, detail=False):
"""List servers, returned detailed output if detailed is True"""
path = '/servers/detail' if detail else '/servers'
- reply = self._get(path)
+ reply = self.http_get(path)
return reply['servers']['values']
def get_server_details(self, server_id):
"""Return detailed output on a server specified by its id"""
path = '/servers/%d' % server_id
- reply = self._get(path)
+ reply = self.http_get(path)
return reply['server']
def create_server(self, name, flavor_id, image_id, personality=None):
req['personality'] = personality
body = json.dumps({'server': req})
- reply = self._post('/servers', body)
+ reply = self.http_post('/servers', body)
return reply['server']
def update_server_name(self, server_id, new_name):
"""
path = '/servers/%d' % server_id
body = json.dumps({'server': {'name': new_name}})
- self._put(path, body)
+ self.http_put(path, body)
def delete_server(self, server_id):
"""Submit a deletion request for a server specified by id"""
path = '/servers/%d' % server_id
- self._delete(path)
+ self.http_delete(path)
def reboot_server(self, server_id, hard=False):
"""Submit a reboot request for a server specified by id"""
path = '/servers/%d/action' % server_id
type = 'HARD' if hard else 'SOFT'
body = json.dumps({'reboot': {'type': type}})
- self._post(path, body)
+ self.http_post(path, body)
def start_server(self, server_id):
"""Submit a startup request for a server specified by id"""
path = '/servers/%d/action' % server_id
body = json.dumps({'start': {}})
- self._post(path, body)
+ self.http_post(path, body)
def shutdown_server(self, server_id):
"""Submit a shutdown request for a server specified by id"""
path = '/servers/%d/action' % server_id
body = json.dumps({'shutdown': {}})
- self._post(path, body)
+ self.http_post(path, body)
def get_server_console(self, server_id):
"""Get a VNC connection to the console of a server specified by id"""
path = '/servers/%d/action' % server_id
body = json.dumps({'console': {'type': 'vnc'}})
- reply = self._post(path, body, 200)
+ reply = self.http_post(path, body, success=200)
return reply['console']
def set_firewall_profile(self, server_id, profile):
The server is specified by id, the profile argument
is one of (ENABLED, DISABLED, PROTECTED).
-
"""
path = '/servers/%d/action' % server_id
body = json.dumps({'firewallProfile': {'profile': profile}})
- self._post(path, body, 202)
+ self.http_post(path, body)
def list_server_addresses(self, server_id, network=None):
path = '/servers/%d/ips' % server_id
if network:
path += '/%s' % network
- reply = self._get(path)
+ reply = self.http_get(path)
return [reply['network']] if network else reply['addresses']['values']
def get_server_metadata(self, server_id, key=None):
path = '/servers/%d/meta' % server_id
if key:
path += '/%s' % key
- reply = self._get(path)
+ reply = self.http_get(path)
return reply['meta'] if key else reply['metadata']['values']
def create_server_metadata(self, server_id, key, val):
path = '/servers/%d/meta/%s' % (server_id, key)
body = json.dumps({'meta': {key: val}})
- reply = self._put(path, body, 201)
+ reply = self.http_put(path, body, 201)
return reply['meta']
def update_server_metadata(self, server_id, **metadata):
path = '/servers/%d/meta' % server_id
body = json.dumps({'metadata': metadata})
- reply = self._post(path, body, 201)
+ reply = self.http_post(path, body, success=201)
return reply['metadata']
def delete_server_metadata(self, server_id, key):
path = '/servers/%d/meta/%s' % (server_id, key)
- reply = self._delete(path)
+ reply = self.http_delete(path)
def get_server_stats(self, server_id):
path = '/servers/%d/stats' % server_id
- reply = self._get(path)
+ reply = self.http_get(path)
return reply['stats']
def list_flavors(self, detail=False):
path = '/flavors/detail' if detail else '/flavors'
- reply = self._get(path)
+ reply = self.http_get(path)
return reply['flavors']['values']
def get_flavor_details(self, flavor_id):
path = '/flavors/%d' % flavor_id
- reply = self._get(path)
+ reply = self.http_get(path)
return reply['flavor']
def list_images(self, detail=False):
path = '/images/detail' if detail else '/images'
- reply = self._get(path)
+ reply = self.http_get(path)
return reply['images']['values']
def get_image_details(self, image_id):
path = '/images/%d' % image_id
- reply = self._get(path)
+ reply = self.http_get(path)
return reply['image']
def create_image(self, server_id, name):
req = {'name': name, 'serverRef': server_id}
body = json.dumps({'image': req})
- reply = self._post('/images', body)
+ reply = self.http_post('/images', body)
return reply['image']
def delete_image(self, image_id):
path = '/images/%d' % image_id
- self._delete(path)
+ self.http_delete(path)
def get_image_metadata(self, image_id, key=None):
path = '/images/%d/meta' % image_id
if key:
path += '/%s' % key
- reply = self._get(path)
+ reply = self.http_get(path)
return reply['meta'] if key else reply['metadata']['values']
def create_image_metadata(self, image_id, key, val):
path = '/images/%d/meta/%s' % (image_id, key)
body = json.dumps({'meta': {key: val}})
- reply = self._put(path, body, 201)
+ reply = self.http_put(path, body, 201)
reply['meta']
def update_image_metadata(self, image_id, **metadata):
path = '/images/%d/meta' % image_id
body = json.dumps({'metadata': metadata})
- reply = self._post(path, body, 201)
+ reply = self.http_post(path, body, success=201)
return reply['metadata']
def delete_image_metadata(self, image_id, key):
path = '/images/%d/meta/%s' % (image_id, key)
- reply = self._delete(path)
+ reply = self.http_delete(path)
# Networks
def list_networks(self, detail=False):
path = '/networks/detail' if detail else '/networks'
- reply = self._get(path)
+ reply = self.http_get(path)
return reply['networks']['values']
def create_network(self, name):
body = json.dumps({'network': {'name': name}})
- reply = self._post('/networks', body)
+ reply = self.http_post('/networks', body)
return reply['network']
def get_network_details(self, network_id):
path = '/networks/%s' % network_id
- reply = self._get(path)
+ reply = self.http_get(path)
return reply['network']
def update_network_name(self, network_id, new_name):
path = '/networks/%s' % network_id
body = json.dumps({'network': {'name': new_name}})
- self._put(path, body)
+ self.http_put(path, body)
def delete_network(self, network_id):
path = '/networks/%s' % network_id
- self._delete(path)
+ self.http_delete(path)
def connect_server(self, server_id, network_id):
path = '/networks/%s/action' % network_id
body = json.dumps({'add': {'serverRef': server_id}})
- self._post(path, body)
+ self.http_post(path, body)
def disconnect_server(self, server_id, network_id):
path = '/networks/%s/action' % network_id
body = json.dumps({'remove': {'serverRef': server_id}})
- self._post(path, body)
+ self.http_post(path, body)
-class ImagesClient(Client):
- def list_public(self, detail=False):
+class GlanceClient(Client):
+ def list_public(self, detail=False, filters={}, order=''):
path = '/images/detail' if detail else '/images/'
- return self._get(path)
+ params = {}
+ params.update(filters)
+
+ if order.startswith('-'):
+ params['sort_dir'] = 'desc'
+ order = order[1:]
+ else:
+ params['sort_dir'] = 'asc'
+
+ if order:
+ params['sort_key'] = order
+
+ if params:
+ path += '?' + '&'.join('%s=%s' % item for item in params.items())
+ return self.http_get(path)
+
+ def register(self, name, location, params={}, properties={}):
+ path = '/images/'
+ headers = {}
+ headers['x-image-meta-name'] = quote(name)
+ headers['x-image-meta-location'] = location
+ for key, val in params.items():
+ if key in ('id', 'store', 'disk_format', 'container_format',
+ 'size', 'checksum', 'is_public', 'owner'):
+ key = 'x-image-meta-' + key.replace('_', '-')
+ headers[key] = val
+ for key, val in properties.items():
+ headers['x-image-meta-property-' + quote(key)] = quote(val)
+ return self.http_post(path, headers=headers, success=200)