Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / servers.py @ 85a634e6

History | View | Annotate | Download (21.2 kB)

1 6ef51e9f Giorgos Verigakis
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2 ce55f211 Kostas Papadimitriou
#
3 adee02b8 Giorgos Verigakis
# Redistribution and use in source and binary forms, with or
4 adee02b8 Giorgos Verigakis
# without modification, are permitted provided that the following
5 adee02b8 Giorgos Verigakis
# conditions are met:
6 ce55f211 Kostas Papadimitriou
#
7 adee02b8 Giorgos Verigakis
#   1. Redistributions of source code must retain the above
8 adee02b8 Giorgos Verigakis
#      copyright notice, this list of conditions and the following
9 adee02b8 Giorgos Verigakis
#      disclaimer.
10 ce55f211 Kostas Papadimitriou
#
11 adee02b8 Giorgos Verigakis
#   2. Redistributions in binary form must reproduce the above
12 adee02b8 Giorgos Verigakis
#      copyright notice, this list of conditions and the following
13 adee02b8 Giorgos Verigakis
#      disclaimer in the documentation and/or other materials
14 adee02b8 Giorgos Verigakis
#      provided with the distribution.
15 ce55f211 Kostas Papadimitriou
#
16 adee02b8 Giorgos Verigakis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 adee02b8 Giorgos Verigakis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 adee02b8 Giorgos Verigakis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 adee02b8 Giorgos Verigakis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 adee02b8 Giorgos Verigakis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 adee02b8 Giorgos Verigakis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 adee02b8 Giorgos Verigakis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 adee02b8 Giorgos Verigakis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 adee02b8 Giorgos Verigakis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 adee02b8 Giorgos Verigakis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 adee02b8 Giorgos Verigakis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 adee02b8 Giorgos Verigakis
# POSSIBILITY OF SUCH DAMAGE.
28 ce55f211 Kostas Papadimitriou
#
29 adee02b8 Giorgos Verigakis
# The views and conclusions contained in the software and
30 adee02b8 Giorgos Verigakis
# documentation are those of the authors and should not be
31 adee02b8 Giorgos Verigakis
# interpreted as representing official policies, either expressed
32 adee02b8 Giorgos Verigakis
# or implied, of GRNET S.A.
33 7e2f9d4b Giorgos Verigakis
34 fc443bcd Giorgos Verigakis
from base64 import b64decode
35 2035039b Giorgos Verigakis
from logging import getLogger
36 fc443bcd Giorgos Verigakis
37 ccd0d474 Giorgos Verigakis
from django.conf import settings
38 d8e50a39 Giorgos Verigakis
from django.conf.urls.defaults import patterns
39 4dba0480 Christos Stavrakakis
from django.db import transaction
40 7e2f9d4b Giorgos Verigakis
from django.http import HttpResponse
41 7e2f9d4b Giorgos Verigakis
from django.template.loader import render_to_string
42 29a59bc1 Giorgos Verigakis
from django.utils import simplejson as json
43 7e2f9d4b Giorgos Verigakis
44 ccd0d474 Giorgos Verigakis
from synnefo.api import faults, util
45 0971fa71 Markos Gogoulos
from synnefo.api.actions import server_actions
46 d8e50a39 Giorgos Verigakis
from synnefo.api.common import method_not_allowed
47 f5b4f2a3 Christos Stavrakakis
from synnefo.db.models import Backend, VirtualMachine, VirtualMachineMetadata
48 529178b1 Giorgos Verigakis
from synnefo.logic.backend import create_instance, delete_instance
49 d8e50a39 Giorgos Verigakis
from synnefo.logic.utils import get_rsapi_state
50 529178b1 Giorgos Verigakis
from synnefo.util.rapi import GanetiApiError
51 cc7c0f44 Christos Stavrakakis
from synnefo.logic.backend_allocator import BackendAllocator
52 cc7c0f44 Christos Stavrakakis
53 cc7c0f44 Christos Stavrakakis
from django.utils import importlib
54 cc7c0f44 Christos Stavrakakis
55 7e2f9d4b Giorgos Verigakis
56 9e98ba3c Giorgos Verigakis
57 9e98ba3c Giorgos Verigakis
log = getLogger('synnefo.api')
58 7e2f9d4b Giorgos Verigakis
59 7e2f9d4b Giorgos Verigakis
urlpatterns = patterns('synnefo.api.servers',
60 7e2f9d4b Giorgos Verigakis
    (r'^(?:/|.json|.xml)?$', 'demux'),
61 7e2f9d4b Giorgos Verigakis
    (r'^/detail(?:.json|.xml)?$', 'list_servers', {'detail': True}),
62 7e2f9d4b Giorgos Verigakis
    (r'^/(\d+)(?:.json|.xml)?$', 'server_demux'),
63 b016b476 Giorgos Verigakis
    (r'^/(\d+)/action(?:.json|.xml)?$', 'server_action'),
64 b016b476 Giorgos Verigakis
    (r'^/(\d+)/ips(?:.json|.xml)?$', 'list_addresses'),
65 b016b476 Giorgos Verigakis
    (r'^/(\d+)/ips/(.+?)(?:.json|.xml)?$', 'list_addresses_by_network'),
66 d8e50a39 Giorgos Verigakis
    (r'^/(\d+)/meta(?:.json|.xml)?$', 'metadata_demux'),
67 d8e50a39 Giorgos Verigakis
    (r'^/(\d+)/meta/(.+?)(?:.json|.xml)?$', 'metadata_item_demux'),
68 c738c935 Giorgos Verigakis
    (r'^/(\d+)/stats(?:.json|.xml)?$', 'server_stats'),
69 85a634e6 Kostas Papadimitriou
    (r'^/(\d+)/diagnostics(?:.json)?$', 'get_server_diagnostics'),
70 7e2f9d4b Giorgos Verigakis
)
71 7e2f9d4b Giorgos Verigakis
72 7e2f9d4b Giorgos Verigakis
73 7e2f9d4b Giorgos Verigakis
def demux(request):
74 7e2f9d4b Giorgos Verigakis
    if request.method == 'GET':
75 7e2f9d4b Giorgos Verigakis
        return list_servers(request)
76 7e2f9d4b Giorgos Verigakis
    elif request.method == 'POST':
77 7e2f9d4b Giorgos Verigakis
        return create_server(request)
78 7e2f9d4b Giorgos Verigakis
    else:
79 d8e50a39 Giorgos Verigakis
        return method_not_allowed(request)
80 7e2f9d4b Giorgos Verigakis
81 55cd40be Vangelis Koukis
82 7e2f9d4b Giorgos Verigakis
def server_demux(request, server_id):
83 7e2f9d4b Giorgos Verigakis
    if request.method == 'GET':
84 7e2f9d4b Giorgos Verigakis
        return get_server_details(request, server_id)
85 7e2f9d4b Giorgos Verigakis
    elif request.method == 'PUT':
86 7e2f9d4b Giorgos Verigakis
        return update_server_name(request, server_id)
87 7e2f9d4b Giorgos Verigakis
    elif request.method == 'DELETE':
88 7e2f9d4b Giorgos Verigakis
        return delete_server(request, server_id)
89 7e2f9d4b Giorgos Verigakis
    else:
90 d8e50a39 Giorgos Verigakis
        return method_not_allowed(request)
91 d8e50a39 Giorgos Verigakis
92 55cd40be Vangelis Koukis
93 d8e50a39 Giorgos Verigakis
def metadata_demux(request, server_id):
94 d8e50a39 Giorgos Verigakis
    if request.method == 'GET':
95 d8e50a39 Giorgos Verigakis
        return list_metadata(request, server_id)
96 d8e50a39 Giorgos Verigakis
    elif request.method == 'POST':
97 d8e50a39 Giorgos Verigakis
        return update_metadata(request, server_id)
98 d8e50a39 Giorgos Verigakis
    else:
99 d8e50a39 Giorgos Verigakis
        return method_not_allowed(request)
100 d8e50a39 Giorgos Verigakis
101 55cd40be Vangelis Koukis
102 d8e50a39 Giorgos Verigakis
def metadata_item_demux(request, server_id, key):
103 d8e50a39 Giorgos Verigakis
    if request.method == 'GET':
104 d8e50a39 Giorgos Verigakis
        return get_metadata_item(request, server_id, key)
105 d8e50a39 Giorgos Verigakis
    elif request.method == 'PUT':
106 d8e50a39 Giorgos Verigakis
        return create_metadata_item(request, server_id, key)
107 d8e50a39 Giorgos Verigakis
    elif request.method == 'DELETE':
108 d8e50a39 Giorgos Verigakis
        return delete_metadata_item(request, server_id, key)
109 d8e50a39 Giorgos Verigakis
    else:
110 d8e50a39 Giorgos Verigakis
        return method_not_allowed(request)
111 7e2f9d4b Giorgos Verigakis
112 d44c236b Giorgos Verigakis
def nic_to_dict(nic):
113 207b70d5 Giorgos Verigakis
    network = nic.network
114 207b70d5 Giorgos Verigakis
    network_id = str(network.id) if not network.public else 'public'
115 08b079e2 Stavros Sachtouris
    ipv4 = nic.ipv4 if nic.ipv4 else None
116 08b079e2 Stavros Sachtouris
    ipv6 = nic.ipv6 if nic.ipv6 else None
117 08b079e2 Stavros Sachtouris
118 08b079e2 Stavros Sachtouris
    d = {'id': util.construct_nic_id(nic), 'network_id': network_id, 'mac_address': nic.mac, 'ipv4': ipv4, 'ipv6': ipv6}
119 207b70d5 Giorgos Verigakis
    if nic.firewall_profile:
120 207b70d5 Giorgos Verigakis
        d['firewallProfile'] = nic.firewall_profile
121 d44c236b Giorgos Verigakis
    return d
122 b016b476 Giorgos Verigakis
123 55cd40be Vangelis Koukis
124 d8e50a39 Giorgos Verigakis
def vm_to_dict(vm, detail=False):
125 d8e50a39 Giorgos Verigakis
    d = dict(id=vm.id, name=vm.name)
126 7e2f9d4b Giorgos Verigakis
    if detail:
127 d8e50a39 Giorgos Verigakis
        d['status'] = get_rsapi_state(vm)
128 19da4325 Vangelis Koukis
        d['progress'] = 100 if get_rsapi_state(vm) == 'ACTIVE' \
129 19da4325 Vangelis Koukis
                        else vm.buildpercentage
130 d8e50a39 Giorgos Verigakis
        d['hostId'] = vm.hostid
131 b36f78fa Giorgos Verigakis
        d['updated'] = util.isoformat(vm.updated)
132 b36f78fa Giorgos Verigakis
        d['created'] = util.isoformat(vm.created)
133 d8e50a39 Giorgos Verigakis
        d['flavorRef'] = vm.flavor.id
134 936afb7b Giorgos Verigakis
        d['imageRef'] = vm.imageid
135 ce55f211 Kostas Papadimitriou
136 7cc3c7d9 Giorgos Verigakis
        metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
137 7e2f9d4b Giorgos Verigakis
        if metadata:
138 b9a77976 Giorgos Verigakis
            d['metadata'] = {'values': metadata}
139 aa197ee4 Vangelis Koukis
140 08b079e2 Stavros Sachtouris
        attachments = [nic_to_dict(nic) for nic in vm.nics.all()]
141 08b079e2 Stavros Sachtouris
        if attachments:
142 08b079e2 Stavros Sachtouris
            d['attachments'] = {'values': attachments}
143 85a634e6 Kostas Papadimitriou
144 85a634e6 Kostas Papadimitriou
        # include the latest vm diagnostic, if set
145 85a634e6 Kostas Papadimitriou
        diagnostic = vm.get_last_diagnostic()
146 85a634e6 Kostas Papadimitriou
        if diagnostic:
147 85a634e6 Kostas Papadimitriou
            d['diagnostics'] = diagnostics_to_dict([diagnostic])
148 85a634e6 Kostas Papadimitriou
149 7e2f9d4b Giorgos Verigakis
    return d
150 7e2f9d4b Giorgos Verigakis
151 d8e50a39 Giorgos Verigakis
152 85a634e6 Kostas Papadimitriou
def diagnostics_to_dict(diagnostics):
153 85a634e6 Kostas Papadimitriou
    """
154 85a634e6 Kostas Papadimitriou
    Extract api data from diagnostics QuerySet.
155 85a634e6 Kostas Papadimitriou
    """
156 85a634e6 Kostas Papadimitriou
    entries = list()
157 85a634e6 Kostas Papadimitriou
158 85a634e6 Kostas Papadimitriou
    for diagnostic in diagnostics:
159 85a634e6 Kostas Papadimitriou
        # format source date if set
160 85a634e6 Kostas Papadimitriou
        formatted_source_date = None
161 85a634e6 Kostas Papadimitriou
        if diagnostic.source_date:
162 85a634e6 Kostas Papadimitriou
            formatted_source_date = util.isoformat(diagnostic.source_date)
163 85a634e6 Kostas Papadimitriou
164 85a634e6 Kostas Papadimitriou
        entry = {
165 85a634e6 Kostas Papadimitriou
            'source': diagnostic.source,
166 85a634e6 Kostas Papadimitriou
            'created': util.isoformat(diagnostic.created),
167 85a634e6 Kostas Papadimitriou
            'message': diagnostic.message,
168 85a634e6 Kostas Papadimitriou
            'details': diagnostic.details,
169 85a634e6 Kostas Papadimitriou
            'level': diagnostic.level,
170 85a634e6 Kostas Papadimitriou
        }
171 85a634e6 Kostas Papadimitriou
172 85a634e6 Kostas Papadimitriou
        if formatted_source_date:
173 85a634e6 Kostas Papadimitriou
            entry['source_date'] = formatted_source_date
174 85a634e6 Kostas Papadimitriou
175 85a634e6 Kostas Papadimitriou
        entries.append(entry)
176 85a634e6 Kostas Papadimitriou
177 85a634e6 Kostas Papadimitriou
    return entries
178 85a634e6 Kostas Papadimitriou
179 85a634e6 Kostas Papadimitriou
180 d8e50a39 Giorgos Verigakis
def render_server(request, server, status=200):
181 d8e50a39 Giorgos Verigakis
    if request.serialization == 'xml':
182 b36f78fa Giorgos Verigakis
        data = render_to_string('server.xml', {
183 b36f78fa Giorgos Verigakis
            'server': server,
184 b36f78fa Giorgos Verigakis
            'is_root': True})
185 7e2f9d4b Giorgos Verigakis
    else:
186 d8e50a39 Giorgos Verigakis
        data = json.dumps({'server': server})
187 c36934a7 Giorgos Verigakis
    return HttpResponse(data, status=status)
188 aa197ee4 Vangelis Koukis
189 7e2f9d4b Giorgos Verigakis
190 85a634e6 Kostas Papadimitriou
def render_diagnostics(request, diagnostics_dict, status=200):
191 85a634e6 Kostas Papadimitriou
    """
192 85a634e6 Kostas Papadimitriou
    Render diagnostics dictionary to json response.
193 85a634e6 Kostas Papadimitriou
    """
194 85a634e6 Kostas Papadimitriou
    return HttpResponse(json.dumps(diagnostics_dict), status=status)
195 85a634e6 Kostas Papadimitriou
196 85a634e6 Kostas Papadimitriou
197 85a634e6 Kostas Papadimitriou
@util.api_method('GET')
198 85a634e6 Kostas Papadimitriou
def get_server_diagnostics(request, server_id):
199 85a634e6 Kostas Papadimitriou
    """
200 85a634e6 Kostas Papadimitriou
    Virtual machine diagnostics api view.
201 85a634e6 Kostas Papadimitriou
    """
202 85a634e6 Kostas Papadimitriou
    log.debug('server_diagnostics %s', server_id)
203 85a634e6 Kostas Papadimitriou
    vm = util.get_vm(server_id, request.user_uniq)
204 85a634e6 Kostas Papadimitriou
    diagnostics = diagnostics_to_dict(vm.diagnostics.all())
205 85a634e6 Kostas Papadimitriou
    return render_diagnostics(request, diagnostics)
206 85a634e6 Kostas Papadimitriou
207 85a634e6 Kostas Papadimitriou
208 b36f78fa Giorgos Verigakis
@util.api_method('GET')
209 7e2f9d4b Giorgos Verigakis
def list_servers(request, detail=False):
210 7e2f9d4b Giorgos Verigakis
    # Normal Response Codes: 200, 203
211 7e2f9d4b Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
212 7e2f9d4b Giorgos Verigakis
    #                       serviceUnavailable (503),
213 7e2f9d4b Giorgos Verigakis
    #                       unauthorized (401),
214 7e2f9d4b Giorgos Verigakis
    #                       badRequest (400),
215 7e2f9d4b Giorgos Verigakis
    #                       overLimit (413)
216 ce55f211 Kostas Papadimitriou
217 9e98ba3c Giorgos Verigakis
    log.debug('list_servers detail=%s', detail)
218 e524ed5f Kostas Papadimitriou
    user_vms = VirtualMachine.objects.filter(userid=request.user_uniq)
219 ce55f211 Kostas Papadimitriou
220 8b2515a9 Christos Stavrakakis
    since = util.isoparse(request.GET.get('changes-since'))
221 85a634e6 Kostas Papadimitriou
222 d8e50a39 Giorgos Verigakis
    if since:
223 2987cd8a Giorgos Verigakis
        user_vms = user_vms.filter(updated__gte=since)
224 d8e50a39 Giorgos Verigakis
        if not user_vms:
225 d8e50a39 Giorgos Verigakis
            return HttpResponse(status=304)
226 e524ed5f Kostas Papadimitriou
    else:
227 1d7c75e4 Kostas Papadimitriou
        user_vms = user_vms.filter(deleted=False)
228 ce55f211 Kostas Papadimitriou
229 d8e50a39 Giorgos Verigakis
    servers = [vm_to_dict(server, detail) for server in user_vms]
230 aa197ee4 Vangelis Koukis
231 d8e50a39 Giorgos Verigakis
    if request.serialization == 'xml':
232 b36f78fa Giorgos Verigakis
        data = render_to_string('list_servers.xml', {
233 b36f78fa Giorgos Verigakis
            'servers': servers,
234 b36f78fa Giorgos Verigakis
            'detail': detail})
235 7e2f9d4b Giorgos Verigakis
    else:
236 c36934a7 Giorgos Verigakis
        data = json.dumps({'servers': {'values': servers}})
237 aa197ee4 Vangelis Koukis
238 c36934a7 Giorgos Verigakis
    return HttpResponse(data, status=200)
239 7e2f9d4b Giorgos Verigakis
240 55cd40be Vangelis Koukis
241 b36f78fa Giorgos Verigakis
@util.api_method('POST')
242 4dba0480 Christos Stavrakakis
@transaction.commit_on_success
243 7e2f9d4b Giorgos Verigakis
def create_server(request):
244 7e2f9d4b Giorgos Verigakis
    # Normal Response Code: 202
245 7e2f9d4b Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
246 7e2f9d4b Giorgos Verigakis
    #                       serviceUnavailable (503),
247 7e2f9d4b Giorgos Verigakis
    #                       unauthorized (401),
248 7e2f9d4b Giorgos Verigakis
    #                       badMediaType(415),
249 7e2f9d4b Giorgos Verigakis
    #                       itemNotFound (404),
250 7e2f9d4b Giorgos Verigakis
    #                       badRequest (400),
251 7e2f9d4b Giorgos Verigakis
    #                       serverCapacityUnavailable (503),
252 7e2f9d4b Giorgos Verigakis
    #                       overLimit (413)
253 b36f78fa Giorgos Verigakis
    req = util.get_request_dict(request)
254 9e98ba3c Giorgos Verigakis
    log.debug('create_server %s', req)
255 ce55f211 Kostas Papadimitriou
256 7e2f9d4b Giorgos Verigakis
    try:
257 7e2f9d4b Giorgos Verigakis
        server = req['server']
258 7e2f9d4b Giorgos Verigakis
        name = server['name']
259 bc855ce7 Giorgos Verigakis
        metadata = server.get('metadata', {})
260 bc855ce7 Giorgos Verigakis
        assert isinstance(metadata, dict)
261 529178b1 Giorgos Verigakis
        image_id = server['imageRef']
262 529178b1 Giorgos Verigakis
        flavor_id = server['flavorRef']
263 0419e0c1 Giorgos Verigakis
        personality = server.get('personality', [])
264 2b1db26f Giorgos Verigakis
        assert isinstance(personality, list)
265 bc855ce7 Giorgos Verigakis
    except (KeyError, AssertionError):
266 ccd0d474 Giorgos Verigakis
        raise faults.BadRequest("Malformed request")
267 ce55f211 Kostas Papadimitriou
268 fc443bcd Giorgos Verigakis
    if len(personality) > settings.MAX_PERSONALITY:
269 fc443bcd Giorgos Verigakis
        raise faults.OverLimit("Maximum number of personalities exceeded")
270 ce55f211 Kostas Papadimitriou
271 fc443bcd Giorgos Verigakis
    for p in personality:
272 22a3918c Giorgos Verigakis
        # Verify that personalities are well-formed
273 fc443bcd Giorgos Verigakis
        try:
274 fc443bcd Giorgos Verigakis
            assert isinstance(p, dict)
275 22a3918c Giorgos Verigakis
            keys = set(p.keys())
276 22a3918c Giorgos Verigakis
            allowed = set(['contents', 'group', 'mode', 'owner', 'path'])
277 22a3918c Giorgos Verigakis
            assert keys.issubset(allowed)
278 fc443bcd Giorgos Verigakis
            contents = p['contents']
279 fc443bcd Giorgos Verigakis
            if len(contents) > settings.MAX_PERSONALITY_SIZE:
280 fc443bcd Giorgos Verigakis
                # No need to decode if contents already exceed limit
281 fc443bcd Giorgos Verigakis
                raise faults.OverLimit("Maximum size of personality exceeded")
282 fc443bcd Giorgos Verigakis
            if len(b64decode(contents)) > settings.MAX_PERSONALITY_SIZE:
283 fc443bcd Giorgos Verigakis
                raise faults.OverLimit("Maximum size of personality exceeded")
284 fc443bcd Giorgos Verigakis
        except AssertionError:
285 fc443bcd Giorgos Verigakis
            raise faults.BadRequest("Malformed personality in request")
286 ce55f211 Kostas Papadimitriou
287 3a9b3cde Giorgos Verigakis
    image = {}
288 4b3b8688 Giorgos Verigakis
    img = util.get_image(image_id, request.user_uniq)
289 6ef51e9f Giorgos Verigakis
    properties = img.get('properties', {})
290 6ef51e9f Giorgos Verigakis
    image['backend_id'] = img['location']
291 6ef51e9f Giorgos Verigakis
    image['format'] = img['disk_format']
292 6ef51e9f Giorgos Verigakis
    image['metadata'] = dict((key.upper(), val) \
293 6ef51e9f Giorgos Verigakis
                             for key, val in properties.items())
294 ce55f211 Kostas Papadimitriou
295 b36f78fa Giorgos Verigakis
    flavor = util.get_flavor(flavor_id)
296 b36f78fa Giorgos Verigakis
    password = util.random_password()
297 ce55f211 Kostas Papadimitriou
298 4b3b8688 Giorgos Verigakis
    count = VirtualMachine.objects.filter(userid=request.user_uniq,
299 6ef51e9f Giorgos Verigakis
                                          deleted=False).count()
300 ce55f211 Kostas Papadimitriou
301 ce55f211 Kostas Papadimitriou
    # get user limit
302 ce55f211 Kostas Papadimitriou
    vms_limit_for_user = \
303 ce55f211 Kostas Papadimitriou
        settings.VMS_USER_QUOTA.get(request.user_uniq,
304 ce55f211 Kostas Papadimitriou
                settings.MAX_VMS_PER_USER)
305 ce55f211 Kostas Papadimitriou
306 ce55f211 Kostas Papadimitriou
    if count >= vms_limit_for_user:
307 856804af Vangelis Koukis
        raise faults.OverLimit("Server count limit exceeded for your account.")
308 ce55f211 Kostas Papadimitriou
309 cc7c0f44 Christos Stavrakakis
    backend_allocator = BackendAllocator()
310 cc7c0f44 Christos Stavrakakis
    backend = backend_allocator.allocate(flavor)
311 cc7c0f44 Christos Stavrakakis
    if backend is None:
312 cc7c0f44 Christos Stavrakakis
        raise Exception
313 cc7c0f44 Christos Stavrakakis
314 cc7c0f44 Christos Stavrakakis
    # We must save the VM instance now, so that it gets a valid
315 cc7c0f44 Christos Stavrakakis
    # vm.backend_vm_id.
316 b36f78fa Giorgos Verigakis
    vm = VirtualMachine.objects.create(
317 b36f78fa Giorgos Verigakis
        name=name,
318 f5b4f2a3 Christos Stavrakakis
        backend=backend,
319 4b3b8688 Giorgos Verigakis
        userid=request.user_uniq,
320 3a9b3cde Giorgos Verigakis
        imageid=image_id,
321 b36f78fa Giorgos Verigakis
        flavor=flavor)
322 ce55f211 Kostas Papadimitriou
323 1c03e74e Vangelis Koukis
    try:
324 4161cb41 Christos Stavrakakis
        jobID = create_instance(vm, flavor, image, password, personality)
325 4f598167 Giorgos Verigakis
    except GanetiApiError:
326 1c03e74e Vangelis Koukis
        vm.delete()
327 510d53b5 Vangelis Koukis
        raise
328 aa197ee4 Vangelis Koukis
329 4161cb41 Christos Stavrakakis
    vm.backendjobid = jobID
330 4161cb41 Christos Stavrakakis
    vm.save()
331 4161cb41 Christos Stavrakakis
332 bc855ce7 Giorgos Verigakis
    for key, val in metadata.items():
333 b36f78fa Giorgos Verigakis
        VirtualMachineMetadata.objects.create(
334 b36f78fa Giorgos Verigakis
            meta_key=key,
335 b36f78fa Giorgos Verigakis
            meta_value=val,
336 b36f78fa Giorgos Verigakis
            vm=vm)
337 ce55f211 Kostas Papadimitriou
338 6ef51e9f Giorgos Verigakis
    log.info('User %s created vm with %s cpus, %s ram and %s storage',
339 4b3b8688 Giorgos Verigakis
             request.user_uniq, flavor.cpu, flavor.ram, flavor.disk)
340 ce55f211 Kostas Papadimitriou
341 d8e50a39 Giorgos Verigakis
    server = vm_to_dict(vm, detail=True)
342 d8e50a39 Giorgos Verigakis
    server['status'] = 'BUILD'
343 529178b1 Giorgos Verigakis
    server['adminPass'] = password
344 d8e50a39 Giorgos Verigakis
    return render_server(request, server, status=202)
345 7e2f9d4b Giorgos Verigakis
346 55cd40be Vangelis Koukis
347 b36f78fa Giorgos Verigakis
@util.api_method('GET')
348 7e2f9d4b Giorgos Verigakis
def get_server_details(request, server_id):
349 c36934a7 Giorgos Verigakis
    # Normal Response Codes: 200, 203
350 c36934a7 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
351 c36934a7 Giorgos Verigakis
    #                       serviceUnavailable (503),
352 c36934a7 Giorgos Verigakis
    #                       unauthorized (401),
353 c36934a7 Giorgos Verigakis
    #                       badRequest (400),
354 c36934a7 Giorgos Verigakis
    #                       itemNotFound (404),
355 c36934a7 Giorgos Verigakis
    #                       overLimit (413)
356 ce55f211 Kostas Papadimitriou
357 9e98ba3c Giorgos Verigakis
    log.debug('get_server_details %s', server_id)
358 4b3b8688 Giorgos Verigakis
    vm = util.get_vm(server_id, request.user_uniq)
359 d8e50a39 Giorgos Verigakis
    server = vm_to_dict(vm, detail=True)
360 d8e50a39 Giorgos Verigakis
    return render_server(request, server)
361 7e2f9d4b Giorgos Verigakis
362 55cd40be Vangelis Koukis
363 b36f78fa Giorgos Verigakis
@util.api_method('PUT')
364 c36934a7 Giorgos Verigakis
def update_server_name(request, server_id):
365 c36934a7 Giorgos Verigakis
    # Normal Response Code: 204
366 c36934a7 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
367 c36934a7 Giorgos Verigakis
    #                       serviceUnavailable (503),
368 c36934a7 Giorgos Verigakis
    #                       unauthorized (401),
369 c36934a7 Giorgos Verigakis
    #                       badRequest (400),
370 c36934a7 Giorgos Verigakis
    #                       badMediaType(415),
371 c36934a7 Giorgos Verigakis
    #                       itemNotFound (404),
372 c36934a7 Giorgos Verigakis
    #                       buildInProgress (409),
373 c36934a7 Giorgos Verigakis
    #                       overLimit (413)
374 aa197ee4 Vangelis Koukis
375 b36f78fa Giorgos Verigakis
    req = util.get_request_dict(request)
376 9e98ba3c Giorgos Verigakis
    log.debug('update_server_name %s %s', server_id, req)
377 ce55f211 Kostas Papadimitriou
378 7e2f9d4b Giorgos Verigakis
    try:
379 7e2f9d4b Giorgos Verigakis
        name = req['server']['name']
380 529178b1 Giorgos Verigakis
    except (TypeError, KeyError):
381 ccd0d474 Giorgos Verigakis
        raise faults.BadRequest("Malformed request")
382 aa197ee4 Vangelis Koukis
383 4b3b8688 Giorgos Verigakis
    vm = util.get_vm(server_id, request.user_uniq)
384 d8e50a39 Giorgos Verigakis
    vm.name = name
385 d8e50a39 Giorgos Verigakis
    vm.save()
386 aa197ee4 Vangelis Koukis
387 7e2f9d4b Giorgos Verigakis
    return HttpResponse(status=204)
388 7e2f9d4b Giorgos Verigakis
389 55cd40be Vangelis Koukis
390 b36f78fa Giorgos Verigakis
@util.api_method('DELETE')
391 4dba0480 Christos Stavrakakis
@transaction.commit_on_success
392 7e2f9d4b Giorgos Verigakis
def delete_server(request, server_id):
393 c36934a7 Giorgos Verigakis
    # Normal Response Codes: 204
394 c36934a7 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
395 c36934a7 Giorgos Verigakis
    #                       serviceUnavailable (503),
396 c36934a7 Giorgos Verigakis
    #                       unauthorized (401),
397 c36934a7 Giorgos Verigakis
    #                       itemNotFound (404),
398 c36934a7 Giorgos Verigakis
    #                       unauthorized (401),
399 c36934a7 Giorgos Verigakis
    #                       buildInProgress (409),
400 c36934a7 Giorgos Verigakis
    #                       overLimit (413)
401 ce55f211 Kostas Papadimitriou
402 9e98ba3c Giorgos Verigakis
    log.debug('delete_server %s', server_id)
403 4b3b8688 Giorgos Verigakis
    vm = util.get_vm(server_id, request.user_uniq)
404 529178b1 Giorgos Verigakis
    delete_instance(vm)
405 7e2f9d4b Giorgos Verigakis
    return HttpResponse(status=204)
406 b016b476 Giorgos Verigakis
407 55cd40be Vangelis Koukis
408 b36f78fa Giorgos Verigakis
@util.api_method('POST')
409 b016b476 Giorgos Verigakis
def server_action(request, server_id):
410 b36f78fa Giorgos Verigakis
    req = util.get_request_dict(request)
411 9e98ba3c Giorgos Verigakis
    log.debug('server_action %s %s', server_id, req)
412 4b3b8688 Giorgos Verigakis
    vm = util.get_vm(server_id, request.user_uniq)
413 b016b476 Giorgos Verigakis
    if len(req) != 1:
414 ccd0d474 Giorgos Verigakis
        raise faults.BadRequest("Malformed request")
415 aa197ee4 Vangelis Koukis
416 b016b476 Giorgos Verigakis
    key = req.keys()[0]
417 d8e50a39 Giorgos Verigakis
    val = req[key]
418 aa197ee4 Vangelis Koukis
419 d8e50a39 Giorgos Verigakis
    try:
420 d8e50a39 Giorgos Verigakis
        assert isinstance(val, dict)
421 20a51b1e Giorgos Verigakis
        return server_actions[key](request, vm, req[key])
422 d8e50a39 Giorgos Verigakis
    except KeyError:
423 ccd0d474 Giorgos Verigakis
        raise faults.BadRequest("Unknown action")
424 d8e50a39 Giorgos Verigakis
    except AssertionError:
425 ccd0d474 Giorgos Verigakis
        raise faults.BadRequest("Invalid argument")
426 b016b476 Giorgos Verigakis
427 55cd40be Vangelis Koukis
428 b36f78fa Giorgos Verigakis
@util.api_method('GET')
429 b016b476 Giorgos Verigakis
def list_addresses(request, server_id):
430 b016b476 Giorgos Verigakis
    # Normal Response Codes: 200, 203
431 b016b476 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
432 b016b476 Giorgos Verigakis
    #                       serviceUnavailable (503),
433 b016b476 Giorgos Verigakis
    #                       unauthorized (401),
434 b016b476 Giorgos Verigakis
    #                       badRequest (400),
435 b016b476 Giorgos Verigakis
    #                       overLimit (413)
436 ce55f211 Kostas Papadimitriou
437 9e98ba3c Giorgos Verigakis
    log.debug('list_addresses %s', server_id)
438 4b3b8688 Giorgos Verigakis
    vm = util.get_vm(server_id, request.user_uniq)
439 d44c236b Giorgos Verigakis
    addresses = [nic_to_dict(nic) for nic in vm.nics.all()]
440 ce55f211 Kostas Papadimitriou
441 d8e50a39 Giorgos Verigakis
    if request.serialization == 'xml':
442 b016b476 Giorgos Verigakis
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
443 b016b476 Giorgos Verigakis
    else:
444 b016b476 Giorgos Verigakis
        data = json.dumps({'addresses': {'values': addresses}})
445 aa197ee4 Vangelis Koukis
446 b016b476 Giorgos Verigakis
    return HttpResponse(data, status=200)
447 b016b476 Giorgos Verigakis
448 55cd40be Vangelis Koukis
449 b36f78fa Giorgos Verigakis
@util.api_method('GET')
450 b016b476 Giorgos Verigakis
def list_addresses_by_network(request, server_id, network_id):
451 b016b476 Giorgos Verigakis
    # Normal Response Codes: 200, 203
452 b016b476 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
453 b016b476 Giorgos Verigakis
    #                       serviceUnavailable (503),
454 b016b476 Giorgos Verigakis
    #                       unauthorized (401),
455 b016b476 Giorgos Verigakis
    #                       badRequest (400),
456 b016b476 Giorgos Verigakis
    #                       itemNotFound (404),
457 b016b476 Giorgos Verigakis
    #                       overLimit (413)
458 ce55f211 Kostas Papadimitriou
459 9e98ba3c Giorgos Verigakis
    log.debug('list_addresses_by_network %s %s', server_id, network_id)
460 4b3b8688 Giorgos Verigakis
    machine = util.get_vm(server_id, request.user_uniq)
461 4b3b8688 Giorgos Verigakis
    network = util.get_network(network_id, request.user_uniq)
462 b36f78fa Giorgos Verigakis
    nic = util.get_nic(machine, network)
463 d44c236b Giorgos Verigakis
    address = nic_to_dict(nic)
464 ce55f211 Kostas Papadimitriou
465 d8e50a39 Giorgos Verigakis
    if request.serialization == 'xml':
466 b016b476 Giorgos Verigakis
        data = render_to_string('address.xml', {'address': address})
467 b016b476 Giorgos Verigakis
    else:
468 b016b476 Giorgos Verigakis
        data = json.dumps({'network': address})
469 aa197ee4 Vangelis Koukis
470 b016b476 Giorgos Verigakis
    return HttpResponse(data, status=200)
471 d8e50a39 Giorgos Verigakis
472 55cd40be Vangelis Koukis
473 b36f78fa Giorgos Verigakis
@util.api_method('GET')
474 d8e50a39 Giorgos Verigakis
def list_metadata(request, server_id):
475 d8e50a39 Giorgos Verigakis
    # Normal Response Codes: 200, 203
476 d8e50a39 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
477 d8e50a39 Giorgos Verigakis
    #                       serviceUnavailable (503),
478 d8e50a39 Giorgos Verigakis
    #                       unauthorized (401),
479 d8e50a39 Giorgos Verigakis
    #                       badRequest (400),
480 d8e50a39 Giorgos Verigakis
    #                       overLimit (413)
481 ce55f211 Kostas Papadimitriou
482 9e98ba3c Giorgos Verigakis
    log.debug('list_server_metadata %s', server_id)
483 4b3b8688 Giorgos Verigakis
    vm = util.get_vm(server_id, request.user_uniq)
484 7cc3c7d9 Giorgos Verigakis
    metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
485 b36f78fa Giorgos Verigakis
    return util.render_metadata(request, metadata, use_values=True, status=200)
486 d8e50a39 Giorgos Verigakis
487 55cd40be Vangelis Koukis
488 b36f78fa Giorgos Verigakis
@util.api_method('POST')
489 d8e50a39 Giorgos Verigakis
def update_metadata(request, server_id):
490 d8e50a39 Giorgos Verigakis
    # Normal Response Code: 201
491 d8e50a39 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
492 d8e50a39 Giorgos Verigakis
    #                       serviceUnavailable (503),
493 d8e50a39 Giorgos Verigakis
    #                       unauthorized (401),
494 d8e50a39 Giorgos Verigakis
    #                       badRequest (400),
495 d8e50a39 Giorgos Verigakis
    #                       buildInProgress (409),
496 d8e50a39 Giorgos Verigakis
    #                       badMediaType(415),
497 d8e50a39 Giorgos Verigakis
    #                       overLimit (413)
498 ce55f211 Kostas Papadimitriou
499 b36f78fa Giorgos Verigakis
    req = util.get_request_dict(request)
500 9e98ba3c Giorgos Verigakis
    log.debug('update_server_metadata %s %s', server_id, req)
501 4b3b8688 Giorgos Verigakis
    vm = util.get_vm(server_id, request.user_uniq)
502 d8e50a39 Giorgos Verigakis
    try:
503 d8e50a39 Giorgos Verigakis
        metadata = req['metadata']
504 d8e50a39 Giorgos Verigakis
        assert isinstance(metadata, dict)
505 d8e50a39 Giorgos Verigakis
    except (KeyError, AssertionError):
506 ccd0d474 Giorgos Verigakis
        raise faults.BadRequest("Malformed request")
507 ce55f211 Kostas Papadimitriou
508 7cc3c7d9 Giorgos Verigakis
    for key, val in metadata.items():
509 7cc3c7d9 Giorgos Verigakis
        meta, created = vm.metadata.get_or_create(meta_key=key)
510 7cc3c7d9 Giorgos Verigakis
        meta.meta_value = val
511 7cc3c7d9 Giorgos Verigakis
        meta.save()
512 ce55f211 Kostas Papadimitriou
513 7cc3c7d9 Giorgos Verigakis
    vm.save()
514 7cc3c7d9 Giorgos Verigakis
    vm_meta = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
515 7cc3c7d9 Giorgos Verigakis
    return util.render_metadata(request, vm_meta, status=201)
516 d8e50a39 Giorgos Verigakis
517 55cd40be Vangelis Koukis
518 b36f78fa Giorgos Verigakis
@util.api_method('GET')
519 d8e50a39 Giorgos Verigakis
def get_metadata_item(request, server_id, key):
520 d8e50a39 Giorgos Verigakis
    # Normal Response Codes: 200, 203
521 d8e50a39 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
522 d8e50a39 Giorgos Verigakis
    #                       serviceUnavailable (503),
523 d8e50a39 Giorgos Verigakis
    #                       unauthorized (401),
524 d8e50a39 Giorgos Verigakis
    #                       itemNotFound (404),
525 d8e50a39 Giorgos Verigakis
    #                       badRequest (400),
526 d8e50a39 Giorgos Verigakis
    #                       overLimit (413)
527 ce55f211 Kostas Papadimitriou
528 9e98ba3c Giorgos Verigakis
    log.debug('get_server_metadata_item %s %s', server_id, key)
529 4b3b8688 Giorgos Verigakis
    vm = util.get_vm(server_id, request.user_uniq)
530 b36f78fa Giorgos Verigakis
    meta = util.get_vm_meta(vm, key)
531 62b894c0 Giorgos Verigakis
    d = {meta.meta_key: meta.meta_value}
532 62b894c0 Giorgos Verigakis
    return util.render_meta(request, d, status=200)
533 d8e50a39 Giorgos Verigakis
534 55cd40be Vangelis Koukis
535 b36f78fa Giorgos Verigakis
@util.api_method('PUT')
536 4dba0480 Christos Stavrakakis
@transaction.commit_on_success
537 d8e50a39 Giorgos Verigakis
def create_metadata_item(request, server_id, key):
538 d8e50a39 Giorgos Verigakis
    # Normal Response Code: 201
539 d8e50a39 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
540 d8e50a39 Giorgos Verigakis
    #                       serviceUnavailable (503),
541 d8e50a39 Giorgos Verigakis
    #                       unauthorized (401),
542 d8e50a39 Giorgos Verigakis
    #                       itemNotFound (404),
543 d8e50a39 Giorgos Verigakis
    #                       badRequest (400),
544 d8e50a39 Giorgos Verigakis
    #                       buildInProgress (409),
545 d8e50a39 Giorgos Verigakis
    #                       badMediaType(415),
546 d8e50a39 Giorgos Verigakis
    #                       overLimit (413)
547 ce55f211 Kostas Papadimitriou
548 b36f78fa Giorgos Verigakis
    req = util.get_request_dict(request)
549 9e98ba3c Giorgos Verigakis
    log.debug('create_server_metadata_item %s %s %s', server_id, key, req)
550 4b3b8688 Giorgos Verigakis
    vm = util.get_vm(server_id, request.user_uniq)
551 d8e50a39 Giorgos Verigakis
    try:
552 d8e50a39 Giorgos Verigakis
        metadict = req['meta']
553 d8e50a39 Giorgos Verigakis
        assert isinstance(metadict, dict)
554 d8e50a39 Giorgos Verigakis
        assert len(metadict) == 1
555 d8e50a39 Giorgos Verigakis
        assert key in metadict
556 d8e50a39 Giorgos Verigakis
    except (KeyError, AssertionError):
557 ccd0d474 Giorgos Verigakis
        raise faults.BadRequest("Malformed request")
558 ce55f211 Kostas Papadimitriou
559 b36f78fa Giorgos Verigakis
    meta, created = VirtualMachineMetadata.objects.get_or_create(
560 b36f78fa Giorgos Verigakis
        meta_key=key,
561 b36f78fa Giorgos Verigakis
        vm=vm)
562 ce55f211 Kostas Papadimitriou
563 d8e50a39 Giorgos Verigakis
    meta.meta_value = metadict[key]
564 d8e50a39 Giorgos Verigakis
    meta.save()
565 5509b599 Giorgos Verigakis
    vm.save()
566 62b894c0 Giorgos Verigakis
    d = {meta.meta_key: meta.meta_value}
567 62b894c0 Giorgos Verigakis
    return util.render_meta(request, d, status=201)
568 d8e50a39 Giorgos Verigakis
569 55cd40be Vangelis Koukis
570 b36f78fa Giorgos Verigakis
@util.api_method('DELETE')
571 4dba0480 Christos Stavrakakis
@transaction.commit_on_success
572 d8e50a39 Giorgos Verigakis
def delete_metadata_item(request, server_id, key):
573 d8e50a39 Giorgos Verigakis
    # Normal Response Code: 204
574 d8e50a39 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
575 d8e50a39 Giorgos Verigakis
    #                       serviceUnavailable (503),
576 d8e50a39 Giorgos Verigakis
    #                       unauthorized (401),
577 d8e50a39 Giorgos Verigakis
    #                       itemNotFound (404),
578 d8e50a39 Giorgos Verigakis
    #                       badRequest (400),
579 d8e50a39 Giorgos Verigakis
    #                       buildInProgress (409),
580 d8e50a39 Giorgos Verigakis
    #                       badMediaType(415),
581 d8e50a39 Giorgos Verigakis
    #                       overLimit (413),
582 ce55f211 Kostas Papadimitriou
583 9e98ba3c Giorgos Verigakis
    log.debug('delete_server_metadata_item %s %s', server_id, key)
584 4b3b8688 Giorgos Verigakis
    vm = util.get_vm(server_id, request.user_uniq)
585 b36f78fa Giorgos Verigakis
    meta = util.get_vm_meta(vm, key)
586 d8e50a39 Giorgos Verigakis
    meta.delete()
587 5509b599 Giorgos Verigakis
    vm.save()
588 d8e50a39 Giorgos Verigakis
    return HttpResponse(status=204)
589 c738c935 Giorgos Verigakis
590 55cd40be Vangelis Koukis
591 c738c935 Giorgos Verigakis
@util.api_method('GET')
592 c738c935 Giorgos Verigakis
def server_stats(request, server_id):
593 c738c935 Giorgos Verigakis
    # Normal Response Codes: 200
594 c738c935 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
595 c738c935 Giorgos Verigakis
    #                       serviceUnavailable (503),
596 c738c935 Giorgos Verigakis
    #                       unauthorized (401),
597 c738c935 Giorgos Verigakis
    #                       badRequest (400),
598 c738c935 Giorgos Verigakis
    #                       itemNotFound (404),
599 c738c935 Giorgos Verigakis
    #                       overLimit (413)
600 ce55f211 Kostas Papadimitriou
601 9e98ba3c Giorgos Verigakis
    log.debug('server_stats %s', server_id)
602 4b3b8688 Giorgos Verigakis
    vm = util.get_vm(server_id, request.user_uniq)
603 924d8085 Christos Stavrakakis
    #secret = util.encrypt(vm.backend_vm_id)
604 924d8085 Christos Stavrakakis
    secret = vm.backend_vm_id      # XXX disable backend id encryption
605 ce55f211 Kostas Papadimitriou
606 c738c935 Giorgos Verigakis
    stats = {
607 c738c935 Giorgos Verigakis
        'serverRef': vm.id,
608 c738c935 Giorgos Verigakis
        'refresh': settings.STATS_REFRESH_PERIOD,
609 5391d6b5 Giorgos Verigakis
        'cpuBar': settings.CPU_BAR_GRAPH_URL % secret,
610 5391d6b5 Giorgos Verigakis
        'cpuTimeSeries': settings.CPU_TIMESERIES_GRAPH_URL % secret,
611 5391d6b5 Giorgos Verigakis
        'netBar': settings.NET_BAR_GRAPH_URL % secret,
612 5391d6b5 Giorgos Verigakis
        'netTimeSeries': settings.NET_TIMESERIES_GRAPH_URL % secret}
613 ce55f211 Kostas Papadimitriou
614 c738c935 Giorgos Verigakis
    if request.serialization == 'xml':
615 c738c935 Giorgos Verigakis
        data = render_to_string('server_stats.xml', stats)
616 c738c935 Giorgos Verigakis
    else:
617 c738c935 Giorgos Verigakis
        data = json.dumps({'stats': stats})
618 c738c935 Giorgos Verigakis
619 c738c935 Giorgos Verigakis
    return HttpResponse(data, status=200)