Commit 504c19cb authored by Giorgos Verigakis's avatar Giorgos Verigakis
Browse files

Add image and flavor support to REDUX API

Refs #301
parent 64227dd6
......@@ -22,3 +22,5 @@ class Unauthorized(Fault):
class ItemNotFound(Fault):
code = 404
class ServiceUnavailable(Fault):
code = 503
\ No newline at end of file
#
# Copyright (c) 2010 Greek Research and Technology Network
#
from synnefo.api.util import *
from synnefo.db.models import Flavor
from django.conf.urls.defaults import *
from django.http import HttpResponse
from django.template.loader import render_to_string
urlpatterns = patterns('synnefo.api.flavors',
(r'^(?:/|.json|.xml)?$', 'list_flavors'),
(r'^/detail(?:.json|.xml)?$', 'list_flavors', {'detail': True}),
(r'^/(\d+)(?:.json|.xml)?$', 'get_flavor_details'),
)
def flavor_to_dict(flavor, detail=True):
d = {'id': flavor.id, 'name': flavor.name}
if detail:
d['ram'] = flavor.ram
d['disk'] = flavor.disk
return d
@api_method('GET')
def list_flavors(request, detail=False):
# Normal Response Codes: 200, 203
# Error Response Codes: computeFault (400, 500),
# serviceUnavailable (503),
# unauthorized (401),
# badRequest (400),
# overLimit (413)
all_flavors = Flavor.objects.all()
flavors = [flavor_to_dict(flavor, detail) for flavor in all_flavors]
if request.type == 'xml':
mimetype = 'application/xml'
data = render_to_string('list_flavors.xml', {'flavors': flavors, 'detail': detail})
else:
mimetype = 'application/json'
data = json.dumps({'flavors': {'values': flavors}})
return HttpResponse(data, mimetype=mimetype, status=200)
@api_method('GET')
def get_flavor_details(request, flavor_id):
# Normal Response Codes: 200, 203
# Error Response Codes: computeFault (400, 500),
# serviceUnavailable (503),
# unauthorized (401),
# badRequest (400),
# itemNotFound (404),
# overLimit (413)
try:
falvor_id = int(flavor_id)
flavor = flavor_to_dict(Flavor.objects.get(id=flavor_id))
except Flavor.DoesNotExist:
raise ItemNotFound
if request.type == 'xml':
data = render_to_string('flavor.xml', {'flavor': flavor})
else:
data = json.dumps({'flavor': flavor})
return HttpResponse(data, status=200)
#
# Copyright (c) 2010 Greek Research and Technology Network
#
from synnefo.api.util import *
from synnefo.db.models import Image
from django.conf.urls.defaults import *
from django.http import HttpResponse
from django.template.loader import render_to_string
urlpatterns = patterns('synnefo.api.images',
(r'^(?:/|.json|.xml)?$', 'demux'),
(r'^/detail(?:.json|.xml)?$', 'list_images', {'detail': True}),
(r'^/(\d+)(?:.json|.xml)?$', 'image_demux'),
)
def demux(request):
if request.method == 'GET':
return list_images(request)
elif request.method == 'POST':
return create_image(request)
else:
fault = BadRequest()
return render_fault(request, fault)
def image_demux(request, image_id):
if request.method == 'GET':
return get_image_details(request, image_id)
elif request.method == 'DELETE':
return delete_image(request, image_id)
else:
fault = BadRequest()
return render_fault(request, fault)
def image_to_dict(image, detail=True):
d = {'id': image.id, 'name': image.name}
if detail:
d['updated'] = image.updated.isoformat()
d['created'] = image.created.isoformat()
d['status'] = image.state
d['progress'] = 100 if image.state == 'ACTIVE' else 0
d['description'] = image.description
if image.sourcevm:
d['serverRef'] = image.sourcevm.id
return d
@api_method('GET')
def list_images(request, detail=False):
# Normal Response Codes: 200, 203
# Error Response Codes: computeFault (400, 500),
# serviceUnavailable (503),
# unauthorized (401),
# badRequest (400),
# overLimit (413)
all_images = Image.objects.all()
images = [image_to_dict(image, detail) for image in all_images]
if request.type == 'xml':
mimetype = 'application/xml'
data = render_to_string('list_images.xml', {'images': images, 'detail': detail})
else:
mimetype = 'application/json'
data = json.dumps({'images': {'values': images}})
return HttpResponse(data, mimetype=mimetype, status=200)
@api_method('POST')
def create_image(request):
# Normal Response Code: 202
# Error Response Codes: computeFault (400, 500),
# serviceUnavailable (503),
# unauthorized (401),
# badMediaType(415),
# itemNotFound (404),
# badRequest (400),
# serverCapacityUnavailable (503),
# buildInProgress (409),
# resizeNotAllowed (403),
# backupOrResizeInProgress (409),
# overLimit (413)
req = get_request_dict(request)
owner = get_user()
try:
d = req['image']
server_id = int(d['serverRef'])
vm = VirtualMachine.objects.get(id=server_id)
image = Image.objects.create(name=d['name'], size=0, owner=owner, sourcevm=vm)
image.save()
except KeyError:
raise BadRequest
except ValueError:
raise BadRequest
except VirtualMachine.DoesNotExist:
raise ItemNotFound
imagedict = image_to_dict(image)
if request.type == 'xml':
data = render_to_string('image.xml', {'image': imagedict})
else:
data = json.dumps({'image': imagedict})
return HttpResponse(data, status=202)
@api_method('GET')
def get_image_details(request, image_id):
# Normal Response Codes: 200, 203
# Error Response Codes: computeFault (400, 500),
# serviceUnavailable (503),
# unauthorized (401),
# badRequest (400),
# itemNotFound (404),
# overLimit (413)
try:
image_id = int(image_id)
imagedict = image_to_dict(Image.objects.get(id=image_id))
except Image.DoesNotExist:
raise ItemNotFound
if request.type == 'xml':
data = render_to_string('image.xml', {'image': imagedict})
else:
data = json.dumps({'image': imagedict})
return HttpResponse(data, status=200)
@api_method('DELETE')
def delete_image(request, image_id):
# Normal Response Code: 204
# Error Response Codes: computeFault (400, 500),
# serviceUnavailable (503),
# unauthorized (401),
# itemNotFound (404),
# overLimit (413)
try:
image_id = int(image_id)
image = Image.objects.get(id=image_id)
except Image.DoesNotExist:
raise ItemNotFound
if image.owner != get_user():
raise Unauthorized()
image.delete()
return HttpResponse(status=204)
......@@ -32,7 +32,8 @@ def demux(request):
elif request.method == 'POST':
return create_server(request)
else:
return HttpResponse(status=404)
fault = BadRequest()
return render_fault(request, fault)
def server_demux(request, server_id):
if request.method == 'GET':
......@@ -42,41 +43,41 @@ def server_demux(request, server_id):
elif request.method == 'DELETE':
return delete_server(request, server_id)
else:
return HttpResponse(status=404)
fault = BadRequest()
return render_fault(request, fault)
def server_dict(vm, detail=False):
d = dict(id=vm.id, name=vm.name)
def server_to_dict(server, detail=False):
d = dict(id=server.id, name=server.name)
if detail:
d['status'] = vm.rsapi_state
d['progress'] = 100 if vm.rsapi_state == 'ACTIVE' else 0
d['hostId'] = vm.hostid
d['updated'] = vm.updated.isoformat()
d['created'] = vm.created.isoformat()
d['flavorId'] = vm.flavor.id # XXX Should use flavorRef instead?
d['imageId'] = vm.sourceimage.id # XXX Should use imageRef instead?
d['description'] = vm.description # XXX Not in OpenStack docs
d['status'] = server.rsapi_state
d['progress'] = 100 if server.rsapi_state == 'ACTIVE' else 0
d['hostId'] = server.hostid
d['updated'] = server.updated.isoformat()
d['created'] = server.created.isoformat()
d['flavorId'] = server.flavor.id # XXX Should use flavorRef instead?
d['imageId'] = server.sourceimage.id # XXX Should use imageRef instead?
d['description'] = server.description # XXX Not in OpenStack docs
vm_meta = vm.virtualmachinemetadata_set.all()
metadata = dict((meta.meta_key, meta.meta_value) for meta in vm_meta)
server_meta = server.virtualmachinemetadata_set.all()
metadata = dict((meta.meta_key, meta.meta_value) for meta in server_meta)
if metadata:
d['metadata'] = dict(values=metadata)
public_addrs = [dict(version=4, addr=vm.ipfour), dict(version=6, addr=vm.ipsix)]
public_addrs = [dict(version=4, addr=server.ipfour), dict(version=6, addr=server.ipsix)]
d['addresses'] = {'values': []}
d['addresses']['values'].append({'id': 'public', 'values': public_addrs})
return d
def render_server(server, request, status=200):
def render_server(request, serverdict, status=200):
if request.type == 'xml':
mimetype = 'application/xml'
data = render_to_string('server.xml', dict(server=server, is_root=True))
data = render_to_string('server.xml', dict(server=serverdict, is_root=True))
else:
mimetype = 'application/json'
data = json.dumps({'server': server})
return HttpResponse(data, mimetype=mimetype, status=status)
data = json.dumps({'server': serverdict})
return HttpResponse(data, status=status)
@api_method
@api_method('GET')
def list_servers(request, detail=False):
# Normal Response Codes: 200, 203
# Error Response Codes: computeFault (400, 500),
......@@ -84,18 +85,19 @@ def list_servers(request, detail=False):
# unauthorized (401),
# badRequest (400),
# overLimit (413)
owner = get_user()
vms = VirtualMachine.objects.filter(owner=owner, deleted=False)
servers = [server_dict(vm, detail) for vm in vms]
user_servers = VirtualMachine.objects.filter(owner=owner, deleted=False)
servers = [server_to_dict(server, detail) for server in user_servers]
if request.type == 'xml':
mimetype = 'application/xml'
data = render_to_string('list_servers.xml', dict(servers=servers, detail=detail))
else:
mimetype = 'application/json'
data = json.dumps({'servers': servers})
return HttpResponse(data, mimetype=mimetype, status=200)
data = json.dumps({'servers': {'values': servers}})
return HttpResponse(data, status=200)
@api_method
@api_method('POST')
def create_server(request):
# Normal Response Code: 202
# Error Response Codes: computeFault (400, 500),
......@@ -121,7 +123,7 @@ def create_server(request):
except Flavor.DoesNotExist:
raise ItemNotFound
vm = VirtualMachine.objects.create(
server = VirtualMachine.objects.create(
name=name,
owner=get_user(),
sourceimage=sourceimage,
......@@ -133,7 +135,7 @@ def create_server(request):
name = 'test-server'
dry_run = True
else:
name = vm.backend_id
name = server.backend_id
dry_run = False
jobId = rapi.CreateInstance(
......@@ -149,51 +151,79 @@ def create_server(request):
dry_run=dry_run,
beparams=dict(auto_balance=True, vcpus=flavor.cpu, memory=flavor.ram))
vm.save()
server.save()
log.info('created vm with %s cpus, %s ram and %s storage' % (flavor.cpu, flavor.ram, flavor.disk))
server = server_dict(vm, detail=True)
server['status'] = 'BUILD'
server['adminPass'] = random_password()
return render_server(server, request, status=202)
serverdict = server_to_dict(server, detail=True)
serverdict['status'] = 'BUILD'
serverdict['adminPass'] = random_password()
return render_server(request, serverdict, status=202)
@api_method
@api_method('GET')
def get_server_details(request, server_id):
# Normal Response Codes: 200, 203
# Error Response Codes: computeFault (400, 500),
# serviceUnavailable (503),
# unauthorized (401),
# badRequest (400),
# itemNotFound (404),
# overLimit (413)
try:
vm = VirtualMachine.objects.get(id=int(server_id))
server_id = int(server_id)
server = VirtualMachine.objects.get(id=server_id)
except VirtualMachine.DoesNotExist:
raise NotFound
raise ItemNotFound
server = server_dict(vm, detail=True)
return render_server(server, request)
serverdict = server_to_dict(server, detail=True)
return render_server(request, serverdict)
@api_method
def update_server_name(request, server_id):
@api_method('PUT')
def update_server_name(request, server_id):
# Normal Response Code: 204
# Error Response Codes: computeFault (400, 500),
# serviceUnavailable (503),
# unauthorized (401),
# badRequest (400),
# badMediaType(415),
# itemNotFound (404),
# buildInProgress (409),
# overLimit (413)
req = get_request_dict(request)
try:
name = req['server']['name']
vm = VirtualMachine.objects.get(id=int(server_id))
server_id = int(server_id)
server = VirtualMachine.objects.get(id=server_id)
except KeyError:
raise BadRequest
except VirtualMachine.DoesNotExist:
raise NotFound
raise ItemNotFound
vm.name = name
vm.save()
server.name = name
server.save()
return HttpResponse(status=204)
@api_method
@api_method('DELETE')
def delete_server(request, server_id):
# Normal Response Codes: 204
# Error Response Codes: computeFault (400, 500),
# serviceUnavailable (503),
# unauthorized (401),
# itemNotFound (404),
# unauthorized (401),
# buildInProgress (409),
# overLimit (413)
try:
vm = VirtualMachine.objects.get(id=int(server_id))
server_id = int(server_id)
server = VirtualMachine.objects.get(id=server_id)
except VirtualMachine.DoesNotExist:
raise NotFound
raise ItemNotFound
vm.start_action('DESTROY')
rapi.DeleteInstance(vm.backend_id)
vm.state = 'DESTROYED'
vm.save()
server.start_action('DESTROY')
rapi.DeleteInstance(server.backend_id)
return HttpResponse(status=204)
<?xml version="1.0" encoding="UTF-8"?>
<flavor xmlns="http://docs.openstack.org/compute/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" id="{{ flavor.id }}" name="{{ flavor.name }}" ram="{{ flavor.ram }}" disk="{{ flavor.disk }}">
</flavor>
<?xml version="1.0" encoding="UTF-8"?>
<image xmlns="http://docs.openstack.org/compute/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" id="{{ image.id }}" name="{{ image.name }}" serverRef="{{ image.serverRef }}" updated="{{ image.updated }}" created="{{ image.created }}" status="{{ image.status }}" progress="{{ image.progress }}"> </image>
{% spaceless %}
<?xml version="1.0" encoding="UTF-8"?>
<flavors xmlns="http://docs.openstack.org/compute/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom">
{% for flavor in flavors %}
<flavor id="{{ flavor.id}}" name="{{ flavor.name }}"{% if detail %} ram="{{ flavor.ram }}" disk="{{ flavor.disk }}"{% endif %}>
</flavor>
{% endfor %}
</flavors>
{% endspaceless %}
{% spaceless %}
<?xml version="1.0" encoding="UTF-8"?>
<images xmlns="http://docs.openstack.org/compute/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom">
{% for image in images %}
<image id="{{ image.id }}" name="{{ image.name }}"{% if detail %} updated="{{ image.updated }}" created="{{ image.created }}" status="{{ image.status }}"{% endif %}>
</image>
{% endfor %}
</images>
{% endspaceless %}
......@@ -104,6 +104,8 @@ v10grnet10patterns = patterns('',
# The OpenStack Compute API v1.1 (REDUX)
v11redux_patterns = patterns('',
(r'^servers', include('synnefo.api.servers')),
(r'^flavors', include('synnefo.api.flavors')),
(r'^images', include('synnefo.api.images')),
(r'^.+', notFound), # catch-all
)
......
......@@ -27,6 +27,7 @@ def tag_name(e):
return name if sep else e.tag
def xml_to_dict(s):
# XXX Quick and dirty
def _xml_to_dict(e):
root = {}
d = root[tag_name(e)] = dict(e.items())
......@@ -60,7 +61,7 @@ def random_password(length=8):
return ''.join(choice(pool) for i in range(length))
def render_fault(fault, request):
def render_fault(request, fault):
if settings.DEBUG or request.META.get('SERVER_NAME', None) == 'testserver':
fault.details = format_exc(fault)
if request.type == 'xml':
......@@ -72,23 +73,32 @@ def render_fault(fault, request):
data = json.dumps(d)
return HttpResponse(data, mimetype=mimetype, status=fault.code)
def api_method(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
try:
if request.path.endswith('.json'):
type = 'json'
elif request.path.endswith('.xml'):
type = 'xml'
elif request.META.get('HTTP_ACCEPT', None) == 'application/xml':
type = 'xml'
else:
type = 'json'
request.type = type
return func(request, *args, **kwargs)
except Fault, fault:
return render_fault(fault, request)
except Exception, e:
log.exception('Unexpected error: %s' % e)
return HttpResponse(status=500)
return wrapper
def api_method(http_method):
def decorator(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
try:
if request.path.endswith('.json'):
type = 'json'
elif request.path.endswith('.xml'):
type = 'xml'
elif request.META.get('HTTP_ACCEPT', None) == 'application/xml':
type = 'xml'
else:
type = 'json'
request.type = type
if request.method != http_method:
raise BadRequest()
resp = func(request, *args, **kwargs)
resp['Content-Type'] = 'application/xml' if type == 'xml' else 'application/json'
return resp
except Fault, fault:
return render_fault(request, fault)
except Exception, e:
log.exception('Unexpected error: %s' % e)
fault = ServiceUnavailable()
return render_fault(request, fault)
return wrapper
return decorator
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment