Commit 238b78d6 authored by Vangelis Koukis's avatar Vangelis Koukis
Browse files

Merge branch 'api-tests'

parents e45740ca c3fa31bd
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
# Copyright (c) 2010 Greek Research and Technology Network # Copyright (c) 2010 Greek Research and Technology Network
# #
from socket import getfqdn
from django.conf import settings from django.conf import settings
from django.http import HttpResponse from django.http import HttpResponse
from django.template.loader import render_to_string from django.template.loader import render_to_string
...@@ -74,10 +76,10 @@ def get_console(request, vm, args): ...@@ -74,10 +76,10 @@ def get_console(request, vm, args):
passwd = random_password() passwd = random_password()
request_vnc_forwarding(sport, daddr, dport, passwd) request_vnc_forwarding(sport, daddr, dport, passwd)
vnc = { 'host': '62.217.120.67', 'port': sport, 'password': passwd } vnc = { 'host': getfqdn(), 'port': sport, 'password': passwd }
# Format to be reviewed by [verigak], FIXME # Format to be reviewed by [verigak], FIXME
if request.type == 'xml': if request.serialization == 'xml':
mimetype = 'application/xml' mimetype = 'application/xml'
data = render_to_string('vnc.xml', {'vnc': vnc}) data = render_to_string('vnc.xml', {'vnc': vnc})
else: else:
......
...@@ -249,9 +249,7 @@ ...@@ -249,9 +249,7 @@
"name": "Debian Squeeze", "name": "Debian Squeeze",
"created": "2011-02-06 00:00:00", "created": "2011-02-06 00:00:00",
"updated": "2011-02-06 00:00:00", "updated": "2011-02-06 00:00:00",
"state": "ACTIVE", "state": "ACTIVE"
"description": "Full Debian Squeeze Installation",
"size": 5678
} }
}, },
{ {
...@@ -330,33 +328,8 @@ ...@@ -330,33 +328,8 @@
"created": "2011-02-10 00:00:00", "created": "2011-02-10 00:00:00",
"updated": "2011-02-10 00:00:00", "updated": "2011-02-10 00:00:00",
"state": "ACTIVE", "state": "ACTIVE",
"description": "Full Slackware 13.1 Installation",
"size": 1234,
"owner" : 1, "owner" : 1,
"sourcevm": 1001 "sourcevm": 1001
} }
},
{
"model": "db.VirtualMachineGroup",
"pk": 1,
"fields": {
"name": "one group of vms",
"created": "2011-02-06 00:00:00",
"updated": "2011-02-06 00:00:00",
"owner" : 1,
"machines" : [1001,1002]
}
},
{
"model": "db.VirtualMachineGroup",
"pk": 2,
"fields": {
"name": "a second group of vms",
"created": "2011-02-06 00:00:00",
"updated": "2011-02-06 00:00:00",
"owner" : 1,
"machines" : [1003,1004]
}
} }
] ]
...@@ -6,9 +6,7 @@ ...@@ -6,9 +6,7 @@
"name": "Debian Unstable", "name": "Debian Unstable",
"created": "2011-02-06 00:00:00", "created": "2011-02-06 00:00:00",
"updated": "2011-02-06 00:00:00", "updated": "2011-02-06 00:00:00",
"state": "ACTIVE", "state": "ACTIVE"
"description": "Debian Sid, full installation",
"size": 4096
} }
}, },
{ {
...@@ -18,9 +16,7 @@ ...@@ -18,9 +16,7 @@
"name": "Red Hat Enterprise Linux", "name": "Red Hat Enterprise Linux",
"created": "2011-02-06 00:00:00", "created": "2011-02-06 00:00:00",
"updated": "2011-02-06 00:00:00", "updated": "2011-02-06 00:00:00",
"state": "ACTIVE", "state": "ACTIVE"
"description": "Red Hat Enterprise Linux, full installation",
"size": 2048
} }
}, },
{ {
...@@ -30,9 +26,7 @@ ...@@ -30,9 +26,7 @@
"name": "Ubuntu 10.10", "name": "Ubuntu 10.10",
"created": "2011-02-06 00:00:00", "created": "2011-02-06 00:00:00",
"updated": "2011-02-06 00:00:00", "updated": "2011-02-06 00:00:00",
"state": "ACTIVE", "state": "ACTIVE"
"description": "Ubuntu 10.10, full installation",
"size": 8192
} }
}, },
......
...@@ -7,6 +7,7 @@ from django.http import HttpResponse ...@@ -7,6 +7,7 @@ from django.http import HttpResponse
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import simplejson as json from django.utils import simplejson as json
from synnefo.api.faults import ItemNotFound
from synnefo.api.util import get_user, get_request_dict, api_method from synnefo.api.util import get_user, get_request_dict, api_method
from synnefo.db.models import Flavor from synnefo.db.models import Flavor
......
...@@ -91,7 +91,7 @@ def list_images(request, detail=False): ...@@ -91,7 +91,7 @@ def list_images(request, detail=False):
since = isoparse(request.GET.get('changes-since')) since = isoparse(request.GET.get('changes-since'))
if since: if since:
avail_images = Image.objects.filter(updated__gt=since) avail_images = Image.objects.filter(updated__gte=since)
if not avail_images: if not avail_images:
return HttpResponse(status=304) return HttpResponse(status=304)
else: else:
...@@ -132,8 +132,7 @@ def create_image(request): ...@@ -132,8 +132,7 @@ def create_image(request):
owner = get_user() owner = get_user()
vm = get_vm(server_id) vm = get_vm(server_id)
image = Image.objects.create(name=name, size=0, owner=owner, sourcevm=vm) image = Image.objects.create(name=name, owner=owner, sourcevm=vm)
image.save()
imagedict = image_to_dict(image) imagedict = image_to_dict(image)
if request.serialization == 'xml': if request.serialization == 'xml':
......
...@@ -14,7 +14,7 @@ from synnefo.api.faults import BadRequest, ItemNotFound ...@@ -14,7 +14,7 @@ from synnefo.api.faults import BadRequest, ItemNotFound
from synnefo.api.util import * from synnefo.api.util import *
from synnefo.db.models import Image, Flavor, VirtualMachine, VirtualMachineMetadata from synnefo.db.models import Image, Flavor, VirtualMachine, VirtualMachineMetadata
from synnefo.logic.utils import get_rsapi_state from synnefo.logic.utils import get_rsapi_state
from synnefo.util.rapi import GanetiRapiClient from synnefo.util.rapi import GanetiRapiClient, GanetiApiError
from synnefo.logic import backend from synnefo.logic import backend
import logging import logging
...@@ -120,7 +120,7 @@ def list_servers(request, detail=False): ...@@ -120,7 +120,7 @@ def list_servers(request, detail=False):
since = isoparse(request.GET.get('changes-since')) since = isoparse(request.GET.get('changes-since'))
if since: if since:
user_vms = VirtualMachine.objects.filter(updated__gt=since) user_vms = VirtualMachine.objects.filter(updated__gte=since)
if not user_vms: if not user_vms:
return HttpResponse(status=304) return HttpResponse(status=304)
else: else:
...@@ -151,50 +151,65 @@ def create_server(request): ...@@ -151,50 +151,65 @@ def create_server(request):
try: try:
server = req['server'] server = req['server']
name = server['name'] name = server['name']
metadata = server.get('metadata', {})
assert isinstance(metadata, dict)
sourceimage = Image.objects.get(id=server['imageRef']) sourceimage = Image.objects.get(id=server['imageRef'])
flavor = Flavor.objects.get(id=server['flavorRef']) flavor = Flavor.objects.get(id=server['flavorRef'])
except KeyError: except (KeyError, AssertionError):
raise BadRequest('Malformed request.') raise BadRequest('Malformed request.')
except Image.DoesNotExist: except Image.DoesNotExist:
raise ItemNotFound raise ItemNotFound
except Flavor.DoesNotExist: except Flavor.DoesNotExist:
raise ItemNotFound raise ItemNotFound
vm = VirtualMachine.objects.create( vm = VirtualMachine(
name=name, name=name,
owner=get_user(), owner=get_user(),
sourceimage=sourceimage, sourceimage=sourceimage,
ipfour='0.0.0.0', ipfour='0.0.0.0',
ipsix='::1', ipsix='::1',
flavor=flavor) flavor=flavor)
# Pick a random password for the VM.
# FIXME: This must be passed to the Ganeti OS provider via CreateInstance()
passwd = random_password()
# We *must* save the VM instance now,
# so that it gets a vm.id and vm.backend_id is valid.
vm.save()
if request.META.get('SERVER_NAME', None) == 'testserver': if request.META.get('SERVER_NAME', None) == 'testserver':
name = 'test-server' backend_name = 'test-server'
dry_run = True dry_run = True
else: else:
name = vm.backend_id backend_name = vm.backend_id
dry_run = False dry_run = False
jobId = rapi.CreateInstance( try:
mode='create', jobId = rapi.CreateInstance(
name=name, mode='create',
disk_template='plain', name=backend_name,
disks=[{"size": 2000}], #FIXME: Always ask for a 2GB disk for now disk_template='plain',
nics=[{}], disks=[{"size": 2000}], #FIXME: Always ask for a 2GB disk for now
os='debootstrap+default', #TODO: select OS from imageRef nics=[{}],
ip_check=False, os='debootstrap+default', #TODO: select OS from imageRef
name_check=False, ip_check=False,
pnode=rapi.GetNodes()[0], #TODO: verify if this is necessary name_check=False,
dry_run=dry_run, pnode=rapi.GetNodes()[0], #TODO: verify if this is necessary
beparams=dict(auto_balance=True, vcpus=flavor.cpu, memory=flavor.ram)) dry_run=dry_run,
beparams=dict(auto_balance=True, vcpus=flavor.cpu, memory=flavor.ram))
vm.save() except GanetiApiError:
vm.delete()
raise ServiceUnavailable('Could not create server.')
for key, val in metadata.items():
VirtualMachineMetadata.objects.create(meta_key=key, meta_value=val, vm=vm)
logging.info('created vm with %s cpus, %s ram and %s storage' % (flavor.cpu, flavor.ram, flavor.disk)) logging.info('created vm with %s cpus, %s ram and %s storage' % (flavor.cpu, flavor.ram, flavor.disk))
server = vm_to_dict(vm, detail=True) server = vm_to_dict(vm, detail=True)
server['status'] = 'BUILD' server['status'] = 'BUILD'
server['adminPass'] = random_password() server['adminPass'] = passwd
return render_server(request, server, status=202) return render_server(request, server, status=202)
@api_method('GET') @api_method('GET')
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<title type="text">Version {{ version.id }}</title> <title type="text">Version {{ version.id }}</title>
<updated>{{ version.updated }}</updated> <updated>{{ version.updated }}</updated>
{% for link in version.links %} {% for link in version.links %}
<link rel="{{ link.rel }}" href="{{ link.href }}"/> <link rel="{{ link.rel }}" {% if link.type %}type="{{ link.type }}" {% endif %}href="{{ link.href }}"/>
{% endfor %} {% endfor %}
<content type="text">Version {{ version.id }} {{ version.status }} ({{ version.updated }})</content> <content type="text">Version {{ version.id }} {{ version.status }} ({{ version.updated }})</content>
</entry> </entry>
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
{% endfor %} {% endfor %}
</media-types> </media-types>
{% for link in version.links %} {% for link in version.links %}
<atom:link rel="{{ link.rel }}" href="{{ link.href }}"/> <atom:link rel="{{ link.rel }}" {% if link.type %}type="{{ link.type }}" {% endif %}href="{{ link.href }}"/>
{% endfor %} {% endfor %}
</version> </version>
{% endspaceless %} {% endspaceless %}
This diff is collapsed.
#
# Copyright (c) 2010 Greek Research and Technology Network
#
from django.test import TestCase
from django.test.client import Client
from django.utils import simplejson as json
API = 'v1.1redux'
class APIReduxTestCase(TestCase):
fixtures = [ 'api_redux_test_data' ]
def setUp(self):
self.client = Client()
self.server_id = 0
def create_server_name(self):
self.server_id += 1
return 'server%d' % self.server_id
def test_create_server_json(self):
TEMPLATE = '''
{
"server" : {
"name" : "%(name)s",
"flavorRef" : "%(flavorRef)s",
"imageRef" : "%(imageRef)s"
}
}
'''
def new_server(imageRef=1, flavorRef=1):
name = self.create_server_name()
return name, TEMPLATE % dict(name=name, imageRef=imageRef, flavorRef=flavorRef)
def verify_response(response, name):
assert response.status_code == 202
reply = json.loads(response.content)
server = reply['server']
assert server['name'] == name
assert server['imageRef'] == 1
assert server['flavorRef'] == 1
assert server['status'] == 'BUILD'
assert server['adminPass']
assert server['addresses']
def verify_error(response, code, name):
assert response.status_code == code
reply = json.loads(response.content)
assert name in reply
assert reply[name]['code'] == code
name, data = new_server()
url = '/api/%s/servers' % API
response = self.client.post(url, content_type='application/json', data=data)
verify_response(response, name)
name, data = new_server()
url = '/api/%s/servers.json' % API
response = self.client.post(url, content_type='application/json', data=data)
verify_response(response, name)
name, data = new_server()
url = '/api/%s/servers.json' % API
response = self.client.post(url, content_type='application/json', data=data,
HTTP_ACCEPT='application/xml')
verify_response(response, name)
name, data = new_server(imageRef=0)
url = '/api/%s/servers' % API
response = self.client.post(url, content_type='application/json', data=data)
verify_error(response, 404, 'itemNotFound')
name, data = new_server(flavorRef=0)
url = '/api/%s/servers' % API
response = self.client.post(url, content_type='application/json', data=data)
verify_error(response, 404, 'itemNotFound')
url = '/api/%s/servers' % API
response = self.client.post(url, content_type='application/json', data='INVALID')
verify_error(response, 400, 'badRequest')
...@@ -6,7 +6,9 @@ from datetime import timedelta, tzinfo ...@@ -6,7 +6,9 @@ from datetime import timedelta, tzinfo
from functools import wraps from functools import wraps
from random import choice from random import choice
from string import ascii_letters, digits from string import ascii_letters, digits
from time import time
from traceback import format_exc from traceback import format_exc
from wsgiref.handlers import format_date_time
from django.conf import settings from django.conf import settings
from django.http import HttpResponse from django.http import HttpResponse
...@@ -45,17 +47,18 @@ def isoparse(s): ...@@ -45,17 +47,18 @@ def isoparse(s):
try: try:
since = dateutil.parser.parse(s) since = dateutil.parser.parse(s)
utc_since = since.astimezone(UTC()).replace(tzinfo=None)
except ValueError: except ValueError:
raise BadRequest('Invalid changes-since parameter.') raise BadRequest('Invalid changes-since parameter.')
now = datetime.datetime.now(UTC()) now = datetime.datetime.now()
if since > now: if utc_since > now:
raise BadRequest('changes-since value set in the future.') raise BadRequest('changes-since value set in the future.')
if now - since > timedelta(seconds=settings.POLL_LIMIT): if now - utc_since > timedelta(seconds=settings.POLL_LIMIT):
raise BadRequest('Too old changes-since value.') raise BadRequest('Too old changes-since value.')
return since return utc_since
def random_password(length=8): def random_password(length=8):
pool = ascii_letters + digits pool = ascii_letters + digits
...@@ -121,6 +124,17 @@ def get_request_dict(request): ...@@ -121,6 +124,17 @@ def get_request_dict(request):
raise BadRequest('Unsupported Content-Type.') raise BadRequest('Unsupported Content-Type.')
def update_response_headers(request, response):
if request.serialization == 'xml':
response['Content-Type'] = 'application/xml'
elif request.serialization == 'atom':
response['Content-Type'] = 'application/atom+xml'
else:
response['Content-Type'] = 'application/json'
if request.META.get('SERVER_NAME') == 'testserver':
response['Date'] = format_date_time(time())
def render_metadata(request, metadata, use_values=False, status=200): def render_metadata(request, metadata, use_values=False, status=200):
if request.serialization == 'xml': if request.serialization == 'xml':
data = render_to_string('metadata.xml', {'metadata': metadata}) data = render_to_string('metadata.xml', {'metadata': metadata})
...@@ -147,14 +161,7 @@ def render_fault(request, fault): ...@@ -147,14 +161,7 @@ def render_fault(request, fault):
data = json.dumps(d) data = json.dumps(d)
resp = HttpResponse(data, status=fault.code) resp = HttpResponse(data, status=fault.code)
update_response_headers(request, resp)
if request.serialization == 'xml':
resp['Content-Type'] = 'application/xml'
elif request.serialization == 'atom':
resp['Content-Type'] = 'application/atom+xml'
else:
resp['Content-Type'] = 'application/json'
return resp return resp
...@@ -196,13 +203,7 @@ def api_method(http_method=None, atom_allowed=False): ...@@ -196,13 +203,7 @@ def api_method(http_method=None, atom_allowed=False):
raise BadRequest('Method not allowed.') raise BadRequest('Method not allowed.')
resp = func(request, *args, **kwargs) resp = func(request, *args, **kwargs)
if request.serialization == 'xml': update_response_headers(request, resp)
resp['Content-Type'] = 'application/xml'
elif request.serialization == 'atom':
resp['Content-Type'] = 'application/atom+xml'
else:
resp['Content-Type'] = 'application/json'
return resp return resp
except Fault, fault: except Fault, fault:
......
...@@ -31,6 +31,18 @@ MEDIA_TYPES = [ ...@@ -31,6 +31,18 @@ MEDIA_TYPES = [
{'base': 'application/json', 'type': 'application/vnd.openstack.compute-v1.1+json'} {'base': 'application/json', 'type': 'application/vnd.openstack.compute-v1.1+json'}
] ]
DESCRIBED_BY = [
{
'rel' : 'describedby',
'type' : 'application/pdf',
'href' : 'http://docs.rackspacecloud.com/servers/api/v1.1/cs-devguide-20110125.pdf'
},
{
'rel' : 'describedby',
'type' : 'application/vnd.sun.wadl+xml',
'href' : 'http://docs.rackspacecloud.com/servers/api/v1.1/application.wadl'
}
]
@api_method('GET', atom_allowed=True) @api_method('GET', atom_allowed=True)
def versions_list(request): def versions_list(request):
...@@ -58,6 +70,7 @@ def version_details(request, api_version): ...@@ -58,6 +70,7 @@ def version_details(request, api_version):
# We hardcode to v1.1 since it is the only one we support # We hardcode to v1.1 since it is the only one we support
version = VERSION_1_1.copy() version = VERSION_1_1.copy()
version['links'] = version['links'] + DESCRIBED_BY
if request.serialization == 'xml': if request.serialization == 'xml':
version['media_types'] = MEDIA_TYPES version['media_types'] = MEDIA_TYPES
......
...@@ -100,12 +100,28 @@ ...@@ -100,12 +100,28 @@
"name": "Debian Squeeze", "name": "Debian Squeeze",
"updated": "2011-02-06 00:00:00", "updated": "2011-02-06 00:00:00",
"created": "2011-02-06 00:00:00", "created": "2011-02-06 00:00:00",
"size" : 2000,
"state": "ACTIVE", "state": "ACTIVE",
"description": "Full Debian Squeeze Installation",
"owner" : 30000 "owner" : 30000
} }
}, },
{
"model": "db.ImageMetadata",
"pk": 1,
"fields": {
"meta_key": "description",
"meta_value": "Debian Squeeze, full installation",
"image": 30000
}
},
{
"model": "db.ImageMetadata",
"pk": 2,
"fields": {
"meta_key": "size",
"meta_value": "2048",
"image": 30000
}
},
{ {
"model": "db.VirtualMachine", "model": "db.VirtualMachine",
"pk": 30000,