Revision c36934a7

b/api/errors.py
22 22
class ItemNotFound(Fault):
23 23
    code = 404
24 24

  
25
class ServiceUnavailable(Fault):
26
    code = 503
b/api/flavors.py
1
#
2
# Copyright (c) 2010 Greek Research and Technology Network
3
#
4

  
5
from synnefo.api.util import *
6
from synnefo.db.models import Flavor
7

  
8
from django.conf.urls.defaults import *
9
from django.http import HttpResponse
10
from django.template.loader import render_to_string
11

  
12

  
13
urlpatterns = patterns('synnefo.api.flavors',
14
    (r'^(?:/|.json|.xml)?$', 'list_flavors'),
15
    (r'^/detail(?:.json|.xml)?$', 'list_flavors', {'detail': True}),
16
    (r'^/(\d+)(?:.json|.xml)?$', 'get_flavor_details'),
17
)
18

  
19

  
20
def flavor_to_dict(flavor, detail=True):
21
    d = {'id': flavor.id, 'name': flavor.name}
22
    if detail:
23
        d['ram'] = flavor.ram
24
        d['disk'] = flavor.disk
25
    return d
26

  
27

  
28
@api_method('GET')
29
def list_flavors(request, detail=False):
30
    # Normal Response Codes: 200, 203
31
    # Error Response Codes: computeFault (400, 500),
32
    #                       serviceUnavailable (503),
33
    #                       unauthorized (401),
34
    #                       badRequest (400),
35
    #                       overLimit (413)
36
    
37
    all_flavors = Flavor.objects.all()
38
    flavors = [flavor_to_dict(flavor, detail) for flavor in all_flavors]
39
    
40
    if request.type == 'xml':
41
        mimetype = 'application/xml'
42
        data = render_to_string('list_flavors.xml', {'flavors': flavors, 'detail': detail})
43
    else:
44
        mimetype = 'application/json'
45
        data = json.dumps({'flavors': {'values': flavors}})
46
    
47
    return HttpResponse(data, mimetype=mimetype, status=200)
48

  
49
@api_method('GET')
50
def get_flavor_details(request, flavor_id):
51
    # Normal Response Codes: 200, 203
52
    # Error Response Codes: computeFault (400, 500),
53
    #                       serviceUnavailable (503),
54
    #                       unauthorized (401),
55
    #                       badRequest (400),
56
    #                       itemNotFound (404),
57
    #                       overLimit (413)
58

  
59
    try:
60
        falvor_id = int(flavor_id)
61
        flavor = flavor_to_dict(Flavor.objects.get(id=flavor_id))
62
    except Flavor.DoesNotExist:
63
        raise ItemNotFound
64
    
65
    if request.type == 'xml':
66
        data = render_to_string('flavor.xml', {'flavor': flavor})
67
    else:
68
        data = json.dumps({'flavor': flavor})
69
    
70
    return HttpResponse(data, status=200)
b/api/images.py
1
#
2
# Copyright (c) 2010 Greek Research and Technology Network
3
#
4

  
5
from synnefo.api.util import *
6
from synnefo.db.models import Image
7

  
8
from django.conf.urls.defaults import *
9
from django.http import HttpResponse
10
from django.template.loader import render_to_string
11

  
12

  
13
urlpatterns = patterns('synnefo.api.images',
14
    (r'^(?:/|.json|.xml)?$', 'demux'),
15
    (r'^/detail(?:.json|.xml)?$', 'list_images', {'detail': True}),
16
    (r'^/(\d+)(?:.json|.xml)?$', 'image_demux'),
17
)
18

  
19
def demux(request):
20
    if request.method == 'GET':
21
        return list_images(request)
22
    elif request.method == 'POST':
23
        return create_image(request)
24
    else:
25
        fault = BadRequest()
26
        return render_fault(request, fault)
27

  
28
def image_demux(request, image_id):
29
    if request.method == 'GET':
30
        return get_image_details(request, image_id)
31
    elif request.method == 'DELETE':
32
        return delete_image(request, image_id)
33
    else:
34
        fault = BadRequest()
35
        return render_fault(request, fault)
36

  
37

  
38
def image_to_dict(image, detail=True):
39
    d = {'id': image.id, 'name': image.name}
40
    if detail:
41
        d['updated'] = image.updated.isoformat()
42
        d['created'] = image.created.isoformat()
43
        d['status'] = image.state
44
        d['progress'] = 100 if image.state == 'ACTIVE' else 0
45
        d['description'] = image.description
46
        if image.sourcevm:
47
            d['serverRef'] = image.sourcevm.id
48
    return d
49

  
50

  
51
@api_method('GET')
52
def list_images(request, detail=False):
53
    # Normal Response Codes: 200, 203
54
    # Error Response Codes: computeFault (400, 500),
55
    #                       serviceUnavailable (503),
56
    #                       unauthorized (401),
57
    #                       badRequest (400),
58
    #                       overLimit (413)
59
    
60
    all_images = Image.objects.all()
61
    images = [image_to_dict(image, detail) for image in all_images]
62
    
63
    if request.type == 'xml':
64
        mimetype = 'application/xml'
65
        data = render_to_string('list_images.xml', {'images': images, 'detail': detail})
66
    else:
67
        mimetype = 'application/json'
68
        data = json.dumps({'images': {'values': images}})
69
    
70
    return HttpResponse(data, mimetype=mimetype, status=200)
71

  
72
@api_method('POST')
73
def create_image(request):
74
    # Normal Response Code: 202
75
    # Error Response Codes: computeFault (400, 500),
76
    #                       serviceUnavailable (503),
77
    #                       unauthorized (401),
78
    #                       badMediaType(415),
79
    #                       itemNotFound (404),
80
    #                       badRequest (400),
81
    #                       serverCapacityUnavailable (503),
82
    #                       buildInProgress (409),
83
    #                       resizeNotAllowed (403),
84
    #                       backupOrResizeInProgress (409),
85
    #                       overLimit (413)
86
    
87
    req = get_request_dict(request)
88
    owner = get_user()
89
    
90
    try:
91
        d = req['image']
92
        server_id = int(d['serverRef'])
93
        vm = VirtualMachine.objects.get(id=server_id)
94
        image = Image.objects.create(name=d['name'], size=0, owner=owner, sourcevm=vm)
95
        image.save()
96
    except KeyError:
97
        raise BadRequest
98
    except ValueError:
99
        raise BadRequest
100
    except VirtualMachine.DoesNotExist:
101
        raise ItemNotFound
102
    
103
    imagedict = image_to_dict(image)
104
    if request.type == 'xml':
105
        data = render_to_string('image.xml', {'image': imagedict})
106
    else:
107
        data = json.dumps({'image': imagedict})
108
    
109
    return HttpResponse(data, status=202)
110

  
111
@api_method('GET')
112
def get_image_details(request, image_id):
113
    # Normal Response Codes: 200, 203
114
    # Error Response Codes: computeFault (400, 500),
115
    #                       serviceUnavailable (503),
116
    #                       unauthorized (401),
117
    #                       badRequest (400),
118
    #                       itemNotFound (404),
119
    #                       overLimit (413)
120
    
121
    try:
122
        image_id = int(image_id)
123
        imagedict = image_to_dict(Image.objects.get(id=image_id))
124
    except Image.DoesNotExist:
125
        raise ItemNotFound
126
    
127
    if request.type == 'xml':
128
        data = render_to_string('image.xml', {'image': imagedict})
129
    else:
130
        data = json.dumps({'image': imagedict})
131
    
132
    return HttpResponse(data, status=200)
133

  
134
@api_method('DELETE')
135
def delete_image(request, image_id):
136
    # Normal Response Code: 204
137
    # Error Response Codes: computeFault (400, 500),
138
    #                       serviceUnavailable (503),
139
    #                       unauthorized (401),
140
    #                       itemNotFound (404),
141
    #                       overLimit (413)
142
    
143
    try:
144
        image_id = int(image_id)
145
        image = Image.objects.get(id=image_id)
146
    except Image.DoesNotExist:
147
        raise ItemNotFound
148
    
149
    if image.owner != get_user():
150
        raise Unauthorized()
151
    image.delete()
152
    return HttpResponse(status=204)
b/api/servers.py
32 32
    elif request.method == 'POST':
33 33
        return create_server(request)
34 34
    else:
35
        return HttpResponse(status=404)
35
        fault = BadRequest()
36
        return render_fault(request, fault)
36 37

  
37 38
def server_demux(request, server_id):
38 39
    if request.method == 'GET':
......
42 43
    elif request.method == 'DELETE':
43 44
        return delete_server(request, server_id)
44 45
    else:
45
        return HttpResponse(status=404)
46
        fault = BadRequest()
47
        return render_fault(request, fault)
46 48

  
47
def server_dict(vm, detail=False):
48
    d = dict(id=vm.id, name=vm.name)
49

  
50
def server_to_dict(server, detail=False):
51
    d = dict(id=server.id, name=server.name)
49 52
    if detail:
50
        d['status'] = vm.rsapi_state
51
        d['progress'] = 100 if vm.rsapi_state == 'ACTIVE' else 0
52
        d['hostId'] = vm.hostid
53
        d['updated'] = vm.updated.isoformat()
54
        d['created'] = vm.created.isoformat()
55
        d['flavorId'] = vm.flavor.id            # XXX Should use flavorRef instead?
56
        d['imageId'] = vm.sourceimage.id        # XXX Should use imageRef instead?
57
        d['description'] = vm.description       # XXX Not in OpenStack docs
53
        d['status'] = server.rsapi_state
54
        d['progress'] = 100 if server.rsapi_state == 'ACTIVE' else 0
55
        d['hostId'] = server.hostid
56
        d['updated'] = server.updated.isoformat()
57
        d['created'] = server.created.isoformat()
58
        d['flavorId'] = server.flavor.id            # XXX Should use flavorRef instead?
59
        d['imageId'] = server.sourceimage.id        # XXX Should use imageRef instead?
60
        d['description'] = server.description       # XXX Not in OpenStack docs
58 61
        
59
        vm_meta = vm.virtualmachinemetadata_set.all()
60
        metadata = dict((meta.meta_key, meta.meta_value) for meta in vm_meta)
62
        server_meta = server.virtualmachinemetadata_set.all()
63
        metadata = dict((meta.meta_key, meta.meta_value) for meta in server_meta)
61 64
        if metadata:
62 65
            d['metadata'] = dict(values=metadata)
63 66
        
64
        public_addrs = [dict(version=4, addr=vm.ipfour), dict(version=6, addr=vm.ipsix)]
67
        public_addrs = [dict(version=4, addr=server.ipfour), dict(version=6, addr=server.ipsix)]
65 68
        d['addresses'] = {'values': []}
66 69
        d['addresses']['values'].append({'id': 'public', 'values': public_addrs})
67 70
    return d
68 71

  
69
def render_server(server, request, status=200):
72
def render_server(request, serverdict, status=200):
70 73
    if request.type == 'xml':
71
        mimetype = 'application/xml'
72
        data = render_to_string('server.xml', dict(server=server, is_root=True))
74
        data = render_to_string('server.xml', dict(server=serverdict, is_root=True))
73 75
    else:
74
        mimetype = 'application/json'
75
        data = json.dumps({'server': server})
76
    return HttpResponse(data, mimetype=mimetype, status=status)    
76
        data = json.dumps({'server': serverdict})
77
    return HttpResponse(data, status=status)
77 78

  
78 79

  
79
@api_method
80
@api_method('GET')
80 81
def list_servers(request, detail=False):
81 82
    # Normal Response Codes: 200, 203
82 83
    # Error Response Codes: computeFault (400, 500),
......
84 85
    #                       unauthorized (401),
85 86
    #                       badRequest (400),
86 87
    #                       overLimit (413)
88
    
87 89
    owner = get_user()
88
    vms = VirtualMachine.objects.filter(owner=owner, deleted=False)
89
    servers = [server_dict(vm, detail) for vm in vms]
90
    user_servers = VirtualMachine.objects.filter(owner=owner, deleted=False)
91
    servers = [server_to_dict(server, detail) for server in user_servers]
92
    
90 93
    if request.type == 'xml':
91
        mimetype = 'application/xml'
92 94
        data = render_to_string('list_servers.xml', dict(servers=servers, detail=detail))
93 95
    else:
94
        mimetype = 'application/json'
95
        data = json.dumps({'servers': servers})
96
    return HttpResponse(data, mimetype=mimetype, status=200)
96
        data = json.dumps({'servers': {'values': servers}})
97
    
98
    return HttpResponse(data, status=200)
97 99

  
98
@api_method
100
@api_method('POST')
99 101
def create_server(request):
100 102
    # Normal Response Code: 202
101 103
    # Error Response Codes: computeFault (400, 500),
......
121 123
    except Flavor.DoesNotExist:
122 124
        raise ItemNotFound
123 125
    
124
    vm = VirtualMachine.objects.create(
126
    server = VirtualMachine.objects.create(
125 127
        name=name,
126 128
        owner=get_user(),
127 129
        sourceimage=sourceimage,
......
133 135
        name = 'test-server'
134 136
        dry_run = True
135 137
    else:
136
        name = vm.backend_id
138
        name = server.backend_id
137 139
        dry_run = False
138 140
    
139 141
    jobId = rapi.CreateInstance(
......
149 151
        dry_run=dry_run,
150 152
        beparams=dict(auto_balance=True, vcpus=flavor.cpu, memory=flavor.ram))
151 153
    
152
    vm.save()
154
    server.save()
153 155
        
154 156
    log.info('created vm with %s cpus, %s ram and %s storage' % (flavor.cpu, flavor.ram, flavor.disk))
155 157
    
156
    server = server_dict(vm, detail=True)
157
    server['status'] = 'BUILD'
158
    server['adminPass'] = random_password()
159
    return render_server(server, request, status=202)
158
    serverdict = server_to_dict(server, detail=True)
159
    serverdict['status'] = 'BUILD'
160
    serverdict['adminPass'] = random_password()
161
    return render_server(request, serverdict, status=202)
160 162

  
161
@api_method
163
@api_method('GET')
162 164
def get_server_details(request, server_id):
165
    # Normal Response Codes: 200, 203
166
    # Error Response Codes: computeFault (400, 500),
167
    #                       serviceUnavailable (503),
168
    #                       unauthorized (401),
169
    #                       badRequest (400),
170
    #                       itemNotFound (404),
171
    #                       overLimit (413)
172
    
163 173
    try:
164
        vm = VirtualMachine.objects.get(id=int(server_id))
174
        server_id = int(server_id)
175
        server = VirtualMachine.objects.get(id=server_id)
165 176
    except VirtualMachine.DoesNotExist:
166
        raise NotFound
177
        raise ItemNotFound
167 178
    
168
    server = server_dict(vm, detail=True)
169
    return render_server(server, request)
179
    serverdict = server_to_dict(server, detail=True)
180
    return render_server(request, serverdict)
170 181

  
171
@api_method
172
def update_server_name(request, server_id):    
182
@api_method('PUT')
183
def update_server_name(request, server_id):
184
    # Normal Response Code: 204
185
    # Error Response Codes: computeFault (400, 500),
186
    #                       serviceUnavailable (503),
187
    #                       unauthorized (401),
188
    #                       badRequest (400),
189
    #                       badMediaType(415),
190
    #                       itemNotFound (404),
191
    #                       buildInProgress (409),
192
    #                       overLimit (413)
193
    
173 194
    req = get_request_dict(request)
174 195
    
175 196
    try:
176 197
        name = req['server']['name']
177
        vm = VirtualMachine.objects.get(id=int(server_id))
198
        server_id = int(server_id)
199
        server = VirtualMachine.objects.get(id=server_id)
178 200
    except KeyError:
179 201
        raise BadRequest
180 202
    except VirtualMachine.DoesNotExist:
181
        raise NotFound
203
        raise ItemNotFound
182 204
    
183
    vm.name = name
184
    vm.save()
205
    server.name = name
206
    server.save()
185 207
    
186 208
    return HttpResponse(status=204)
187 209

  
188
@api_method
210
@api_method('DELETE')
189 211
def delete_server(request, server_id):
212
    # Normal Response Codes: 204
213
    # Error Response Codes: computeFault (400, 500),
214
    #                       serviceUnavailable (503),
215
    #                       unauthorized (401),
216
    #                       itemNotFound (404),
217
    #                       unauthorized (401),
218
    #                       buildInProgress (409),
219
    #                       overLimit (413)
220
    
190 221
    try:
191
        vm = VirtualMachine.objects.get(id=int(server_id))
222
        server_id = int(server_id)
223
        server = VirtualMachine.objects.get(id=server_id)
192 224
    except VirtualMachine.DoesNotExist:
193
        raise NotFound
225
        raise ItemNotFound
194 226
    
195
    vm.start_action('DESTROY')
196
    rapi.DeleteInstance(vm.backend_id)
197
    vm.state = 'DESTROYED'
198
    vm.save()
227
    server.start_action('DESTROY')
228
    rapi.DeleteInstance(server.backend_id)
199 229
    return HttpResponse(status=204)
b/api/templates/flavor.xml
1
<?xml version="1.0" encoding="UTF-8"?>
2
<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 }}">
3
</flavor>
b/api/templates/image.xml
1
<?xml version="1.0" encoding="UTF-8"?>
2
<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>
b/api/templates/list_flavors.xml
1
{% spaceless %}
2
<?xml version="1.0" encoding="UTF-8"?>
3
<flavors xmlns="http://docs.openstack.org/compute/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom">
4
  {% for flavor in flavors %}  
5
  <flavor id="{{ flavor.id}}" name="{{ flavor.name }}"{% if detail %} ram="{{ flavor.ram }}" disk="{{ flavor.disk }}"{% endif %}>
6
  </flavor>
7
  {% endfor %}
8
</flavors>
9
{% endspaceless %}
b/api/templates/list_images.xml
1
{% spaceless %}
2
<?xml version="1.0" encoding="UTF-8"?>
3
<images xmlns="http://docs.openstack.org/compute/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom">
4
  {% for image in images %}
5
  <image id="{{ image.id }}" name="{{ image.name }}"{% if detail %} updated="{{ image.updated }}" created="{{ image.created }}" status="{{ image.status }}"{% endif %}>
6
  </image>
7
  {% endfor %}
8
</images>
9
{% endspaceless %}
b/api/urls.py
104 104
# The OpenStack Compute API v1.1 (REDUX)
105 105
v11redux_patterns = patterns('',
106 106
    (r'^servers', include('synnefo.api.servers')),
107
    (r'^flavors', include('synnefo.api.flavors')),
108
    (r'^images', include('synnefo.api.images')),
107 109
    (r'^.+', notFound), # catch-all
108 110
)
109 111

  
b/api/util.py
27 27
    return name if sep else e.tag
28 28

  
29 29
def xml_to_dict(s):
30
    # XXX Quick and dirty
30 31
    def _xml_to_dict(e):
31 32
        root = {}
32 33
        d = root[tag_name(e)] = dict(e.items())
......
60 61
    return ''.join(choice(pool) for i in range(length))
61 62

  
62 63

  
63
def render_fault(fault, request):
64
def render_fault(request, fault):
64 65
    if settings.DEBUG or request.META.get('SERVER_NAME', None) == 'testserver':
65 66
        fault.details = format_exc(fault)
66 67
    if request.type == 'xml':
......
72 73
        data = json.dumps(d)
73 74
    return HttpResponse(data, mimetype=mimetype, status=fault.code)    
74 75

  
75
def api_method(func):
76
    @wraps(func)
77
    def wrapper(request, *args, **kwargs):
78
        try:
79
            if request.path.endswith('.json'):
80
                type = 'json'
81
            elif request.path.endswith('.xml'):
82
                type = 'xml'
83
            elif request.META.get('HTTP_ACCEPT', None) == 'application/xml':
84
                type = 'xml'
85
            else:
86
                type = 'json'
87
            request.type = type
88
            return func(request, *args, **kwargs)
89
        except Fault, fault:
90
            return render_fault(fault, request)
91
        except Exception, e:
92
            log.exception('Unexpected error: %s' % e)
93
            return HttpResponse(status=500)
94
    return wrapper
76
def api_method(http_method):
77
    def decorator(func):
78
        @wraps(func)
79
        def wrapper(request, *args, **kwargs):
80
            try:
81
                if request.path.endswith('.json'):
82
                    type = 'json'
83
                elif request.path.endswith('.xml'):
84
                    type = 'xml'
85
                elif request.META.get('HTTP_ACCEPT', None) == 'application/xml':
86
                    type = 'xml'
87
                else:
88
                    type = 'json'
89
                request.type = type
90
                
91
                if request.method != http_method:
92
                    raise BadRequest()
93
                
94
                resp = func(request, *args, **kwargs)
95
                resp['Content-Type'] = 'application/xml' if type == 'xml' else 'application/json'
96
                return resp
97
            except Fault, fault:
98
                return render_fault(request, fault)
99
            except Exception, e:
100
                log.exception('Unexpected error: %s' % e)
101
                fault = ServiceUnavailable()
102
                return render_fault(request, fault)
103
        return wrapper
104
    return decorator

Also available in: Unified diff