Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / util.py @ be1dca04

History | View | Annotate | Download (17.9 kB)

1 6ef51e9f Giorgos Verigakis
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2 d1387ed7 Christodoulos Psaltis
#
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 d1387ed7 Christodoulos Psaltis
#
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 d1387ed7 Christodoulos Psaltis
#
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 d1387ed7 Christodoulos Psaltis
#
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 d1387ed7 Christodoulos Psaltis
#
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 c738c935 Giorgos Verigakis
import datetime
35 c738c935 Giorgos Verigakis
36 dca7553e Christos Stavrakakis
from base64 import b64encode, b64decode
37 d8e50a39 Giorgos Verigakis
from datetime import timedelta, tzinfo
38 d8e50a39 Giorgos Verigakis
from functools import wraps
39 c738c935 Giorgos Verigakis
from hashlib import sha256
40 2035039b Giorgos Verigakis
from logging import getLogger
41 d8e50a39 Giorgos Verigakis
from random import choice
42 dca2a31f Giorgos Verigakis
from string import digits, lowercase, uppercase
43 8b01f7f3 Giorgos Verigakis
from time import time
44 d8e50a39 Giorgos Verigakis
from traceback import format_exc
45 8b01f7f3 Giorgos Verigakis
from wsgiref.handlers import format_date_time
46 7e2f9d4b Giorgos Verigakis
47 2035039b Giorgos Verigakis
import dateutil.parser
48 2035039b Giorgos Verigakis
49 c738c935 Giorgos Verigakis
from Crypto.Cipher import AES
50 529178b1 Giorgos Verigakis
51 d8e50a39 Giorgos Verigakis
from django.conf import settings
52 7e2f9d4b Giorgos Verigakis
from django.http import HttpResponse
53 7e2f9d4b Giorgos Verigakis
from django.template.loader import render_to_string
54 29a59bc1 Giorgos Verigakis
from django.utils import simplejson as json
55 b75555e5 Giorgos Verigakis
from django.utils.cache import add_never_cache_headers
56 d3406fbc Christos Stavrakakis
from django.db.models import Q
57 7e2f9d4b Giorgos Verigakis
58 b36f78fa Giorgos Verigakis
from synnefo.api.faults import (Fault, BadRequest, BuildInProgress,
59 6dd70a5c Christos Stavrakakis
                                ItemNotFound, ServiceUnavailable, Unauthorized,
60 dca7553e Christos Stavrakakis
                                BadMediaType, Forbidden, OverLimit)
61 6ef51e9f Giorgos Verigakis
from synnefo.db.models import (Flavor, VirtualMachine, VirtualMachineMetadata,
62 7fede91e Christos Stavrakakis
                               Network, BackendNetwork, NetworkInterface,
63 af6a3bc5 Christos Stavrakakis
                               BridgePoolTable, MacPrefixPoolTable)
64 adc46059 Christos Stavrakakis
from synnefo.db.pools import EmptyPool
65 6dd70a5c Christos Stavrakakis
66 4b3b8688 Giorgos Verigakis
from synnefo.lib.astakos import get_user
67 2db7d9df Christos Stavrakakis
from synnefo.plankton.backend import ImageBackend, NotAllowedError
68 b14725eb Christos Stavrakakis
from synnefo.settings import MAX_CIDR_BLOCK
69 9e98ba3c Giorgos Verigakis
70 9e98ba3c Giorgos Verigakis
71 9e98ba3c Giorgos Verigakis
log = getLogger('synnefo.api')
72 9e98ba3c Giorgos Verigakis
73 7e2f9d4b Giorgos Verigakis
74 d8e50a39 Giorgos Verigakis
class UTC(tzinfo):
75 d8e50a39 Giorgos Verigakis
    def utcoffset(self, dt):
76 d8e50a39 Giorgos Verigakis
        return timedelta(0)
77 aa197ee4 Vangelis Koukis
78 d8e50a39 Giorgos Verigakis
    def tzname(self, dt):
79 d8e50a39 Giorgos Verigakis
        return 'UTC'
80 aa197ee4 Vangelis Koukis
81 d8e50a39 Giorgos Verigakis
    def dst(self, dt):
82 d8e50a39 Giorgos Verigakis
        return timedelta(0)
83 7e2f9d4b Giorgos Verigakis
84 7e2f9d4b Giorgos Verigakis
85 d8e50a39 Giorgos Verigakis
def isoformat(d):
86 529178b1 Giorgos Verigakis
    """Return an ISO8601 date string that includes a timezone."""
87 aa197ee4 Vangelis Koukis
88 d8e50a39 Giorgos Verigakis
    return d.replace(tzinfo=UTC()).isoformat()
89 d8e50a39 Giorgos Verigakis
90 a08e4270 Vangelis Koukis
91 d8e50a39 Giorgos Verigakis
def isoparse(s):
92 d8e50a39 Giorgos Verigakis
    """Parse an ISO8601 date string into a datetime object."""
93 aa197ee4 Vangelis Koukis
94 d8e50a39 Giorgos Verigakis
    if not s:
95 d8e50a39 Giorgos Verigakis
        return None
96 aa197ee4 Vangelis Koukis
97 d8e50a39 Giorgos Verigakis
    try:
98 d8e50a39 Giorgos Verigakis
        since = dateutil.parser.parse(s)
99 e87d30f3 Giorgos Verigakis
        utc_since = since.astimezone(UTC()).replace(tzinfo=None)
100 d8e50a39 Giorgos Verigakis
    except ValueError:
101 d8e50a39 Giorgos Verigakis
        raise BadRequest('Invalid changes-since parameter.')
102 aa197ee4 Vangelis Koukis
103 e87d30f3 Giorgos Verigakis
    now = datetime.datetime.now()
104 e87d30f3 Giorgos Verigakis
    if utc_since > now:
105 d8e50a39 Giorgos Verigakis
        raise BadRequest('changes-since value set in the future.')
106 aa197ee4 Vangelis Koukis
107 e87d30f3 Giorgos Verigakis
    if now - utc_since > timedelta(seconds=settings.POLL_LIMIT):
108 d8e50a39 Giorgos Verigakis
        raise BadRequest('Too old changes-since value.')
109 aa197ee4 Vangelis Koukis
110 0140e54b Vangelis Koukis
    return utc_since
111 aa197ee4 Vangelis Koukis
112 dca2a31f Giorgos Verigakis
113 dca2a31f Giorgos Verigakis
def random_password():
114 dca2a31f Giorgos Verigakis
    """Generates a random password
115 ce55f211 Kostas Papadimitriou

116 62eac5a6 Giorgos Verigakis
    We generate a windows compliant password: it must contain at least
117 dca2a31f Giorgos Verigakis
    one charachter from each of the groups: upper case, lower case, digits.
118 dca2a31f Giorgos Verigakis
    """
119 ce55f211 Kostas Papadimitriou
120 dca2a31f Giorgos Verigakis
    pool = lowercase + uppercase + digits
121 dca2a31f Giorgos Verigakis
    lowerset = set(lowercase)
122 dca2a31f Giorgos Verigakis
    upperset = set(uppercase)
123 dca2a31f Giorgos Verigakis
    digitset = set(digits)
124 dca2a31f Giorgos Verigakis
    length = 10
125 ce55f211 Kostas Papadimitriou
126 62eac5a6 Giorgos Verigakis
    password = ''.join(choice(pool) for i in range(length - 2))
127 ce55f211 Kostas Papadimitriou
128 62eac5a6 Giorgos Verigakis
    # Make sure the password is compliant
129 62eac5a6 Giorgos Verigakis
    chars = set(password)
130 62eac5a6 Giorgos Verigakis
    if not chars & lowerset:
131 62eac5a6 Giorgos Verigakis
        password += choice(lowercase)
132 62eac5a6 Giorgos Verigakis
    if not chars & upperset:
133 62eac5a6 Giorgos Verigakis
        password += choice(uppercase)
134 62eac5a6 Giorgos Verigakis
    if not chars & digitset:
135 62eac5a6 Giorgos Verigakis
        password += choice(digits)
136 ce55f211 Kostas Papadimitriou
137 62eac5a6 Giorgos Verigakis
    # Pad if necessary to reach required length
138 62eac5a6 Giorgos Verigakis
    password += ''.join(choice(pool) for i in range(length - len(password)))
139 ce55f211 Kostas Papadimitriou
140 dca2a31f Giorgos Verigakis
    return password
141 dca2a31f Giorgos Verigakis
142 d8e50a39 Giorgos Verigakis
143 c738c935 Giorgos Verigakis
def zeropad(s):
144 c738c935 Giorgos Verigakis
    """Add zeros at the end of a string in order to make its length
145 c738c935 Giorgos Verigakis
       a multiple of 16."""
146 d1387ed7 Christodoulos Psaltis
147 c738c935 Giorgos Verigakis
    npad = 16 - len(s) % 16
148 c738c935 Giorgos Verigakis
    return s + '\x00' * npad
149 c738c935 Giorgos Verigakis
150 a08e4270 Vangelis Koukis
151 c738c935 Giorgos Verigakis
def encrypt(plaintext):
152 c738c935 Giorgos Verigakis
    # Make sure key is 32 bytes long
153 c738c935 Giorgos Verigakis
    key = sha256(settings.SECRET_KEY).digest()
154 d1387ed7 Christodoulos Psaltis
155 c738c935 Giorgos Verigakis
    aes = AES.new(key)
156 c738c935 Giorgos Verigakis
    enc = aes.encrypt(zeropad(plaintext))
157 c738c935 Giorgos Verigakis
    return b64encode(enc)
158 c738c935 Giorgos Verigakis
159 7e2f9d4b Giorgos Verigakis
160 7f2dbcad Christos Stavrakakis
def get_vm(server_id, user_id, for_update=False, non_deleted=False,
161 7f2dbcad Christos Stavrakakis
           non_suspended=False):
162 e221ade2 Christos Stavrakakis
    """Find a VirtualMachine instance based on ID and owner."""
163 aa197ee4 Vangelis Koukis
164 d8e50a39 Giorgos Verigakis
    try:
165 d8e50a39 Giorgos Verigakis
        server_id = int(server_id)
166 7f2dbcad Christos Stavrakakis
        servers = VirtualMachine.objects
167 7f2dbcad Christos Stavrakakis
        if for_update:
168 7f2dbcad Christos Stavrakakis
            servers = servers.select_for_update()
169 7f2dbcad Christos Stavrakakis
        vm = servers.get(id=server_id, userid=user_id)
170 e221ade2 Christos Stavrakakis
        if non_deleted and vm.deleted:
171 e221ade2 Christos Stavrakakis
            raise VirtualMachine.DeletedError
172 e221ade2 Christos Stavrakakis
        if non_suspended and vm.suspended:
173 17c2ed57 Christos Stavrakakis
            raise Forbidden("Administratively Suspended VM")
174 3a522cff Christos Stavrakakis
        return vm
175 d8e50a39 Giorgos Verigakis
    except ValueError:
176 d8e50a39 Giorgos Verigakis
        raise BadRequest('Invalid server ID.')
177 d8e50a39 Giorgos Verigakis
    except VirtualMachine.DoesNotExist:
178 d8e50a39 Giorgos Verigakis
        raise ItemNotFound('Server not found.')
179 d8e50a39 Giorgos Verigakis
180 7f2dbcad Christos Stavrakakis
181 40777cc8 Giorgos Verigakis
def get_vm_meta(vm, key):
182 d8e50a39 Giorgos Verigakis
    """Return a VirtualMachineMetadata instance or raise ItemNotFound."""
183 aa197ee4 Vangelis Koukis
184 d8e50a39 Giorgos Verigakis
    try:
185 40777cc8 Giorgos Verigakis
        return VirtualMachineMetadata.objects.get(meta_key=key, vm=vm)
186 d8e50a39 Giorgos Verigakis
    except VirtualMachineMetadata.DoesNotExist:
187 d8e50a39 Giorgos Verigakis
        raise ItemNotFound('Metadata key not found.')
188 d8e50a39 Giorgos Verigakis
189 a08e4270 Vangelis Koukis
190 6ef51e9f Giorgos Verigakis
def get_image(image_id, user_id):
191 d8e50a39 Giorgos Verigakis
    """Return an Image instance or raise ItemNotFound."""
192 aa197ee4 Vangelis Koukis
193 6ef51e9f Giorgos Verigakis
    backend = ImageBackend(user_id)
194 d8e50a39 Giorgos Verigakis
    try:
195 6ef51e9f Giorgos Verigakis
        image = backend.get_image(image_id)
196 3a9b3cde Giorgos Verigakis
        if not image:
197 3a9b3cde Giorgos Verigakis
            raise ItemNotFound('Image not found.')
198 3a9b3cde Giorgos Verigakis
        return image
199 3a9b3cde Giorgos Verigakis
    finally:
200 3a9b3cde Giorgos Verigakis
        backend.close()
201 3a9b3cde Giorgos Verigakis
202 a08e4270 Vangelis Koukis
203 dca7553e Christos Stavrakakis
def get_image_dict(image_id, user_id):
204 dca7553e Christos Stavrakakis
    image = {}
205 dca7553e Christos Stavrakakis
    img = get_image(image_id, user_id)
206 dca7553e Christos Stavrakakis
    properties = img.get('properties', {})
207 dca7553e Christos Stavrakakis
    image['backend_id'] = img['location']
208 dca7553e Christos Stavrakakis
    image['format'] = img['disk_format']
209 e440e835 Christos Stavrakakis
    image['metadata'] = dict((key.upper(), val)
210 dca7553e Christos Stavrakakis
                             for key, val in properties.items())
211 2cb812be Christos Stavrakakis
    image['checksum'] = img['checksum']
212 2cb812be Christos Stavrakakis
213 dca7553e Christos Stavrakakis
    return image
214 dca7553e Christos Stavrakakis
215 dca7553e Christos Stavrakakis
216 aa8230bd Christos Stavrakakis
def get_flavor(flavor_id, include_deleted=False):
217 529178b1 Giorgos Verigakis
    """Return a Flavor instance or raise ItemNotFound."""
218 aa197ee4 Vangelis Koukis
219 529178b1 Giorgos Verigakis
    try:
220 529178b1 Giorgos Verigakis
        flavor_id = int(flavor_id)
221 aa8230bd Christos Stavrakakis
        if include_deleted:
222 aa8230bd Christos Stavrakakis
            return Flavor.objects.get(id=flavor_id)
223 aa8230bd Christos Stavrakakis
        else:
224 aa8230bd Christos Stavrakakis
            return Flavor.objects.get(id=flavor_id, deleted=include_deleted)
225 6ef51e9f Giorgos Verigakis
    except (ValueError, Flavor.DoesNotExist):
226 529178b1 Giorgos Verigakis
        raise ItemNotFound('Flavor not found.')
227 d8e50a39 Giorgos Verigakis
228 a08e4270 Vangelis Koukis
229 d2e73c0c Christos Stavrakakis
def get_network(network_id, user_id, for_update=False):
230 e2ee7808 Giorgos Verigakis
    """Return a Network instance or raise ItemNotFound."""
231 aa197ee4 Vangelis Koukis
232 e2ee7808 Giorgos Verigakis
    try:
233 7fede91e Christos Stavrakakis
        network_id = int(network_id)
234 d3406fbc Christos Stavrakakis
        objects = Network.objects
235 d2e73c0c Christos Stavrakakis
        if for_update:
236 d3406fbc Christos Stavrakakis
            objects = objects.select_for_update()
237 d3406fbc Christos Stavrakakis
        return objects.get(Q(userid=user_id) | Q(public=True), id=network_id)
238 6ef51e9f Giorgos Verigakis
    except (ValueError, Network.DoesNotExist):
239 e2ee7808 Giorgos Verigakis
        raise ItemNotFound('Network not found.')
240 e2ee7808 Giorgos Verigakis
241 a08e4270 Vangelis Koukis
242 b14725eb Christos Stavrakakis
def validate_network_size(cidr_block):
243 b14725eb Christos Stavrakakis
    """Return True if network size is allowed."""
244 b14725eb Christos Stavrakakis
    return cidr_block <= 29 and cidr_block > MAX_CIDR_BLOCK
245 b14725eb Christos Stavrakakis
246 b14725eb Christos Stavrakakis
247 adc46059 Christos Stavrakakis
def allocate_public_address(backend):
248 adc46059 Christos Stavrakakis
    """Allocate a public IP for a vm."""
249 adc46059 Christos Stavrakakis
    for network in backend_public_networks(backend):
250 adc46059 Christos Stavrakakis
        try:
251 adc46059 Christos Stavrakakis
            address = get_network_free_address(network)
252 adc46059 Christos Stavrakakis
            return (network, address)
253 adc46059 Christos Stavrakakis
        except EmptyPool:
254 adc46059 Christos Stavrakakis
            pass
255 adc46059 Christos Stavrakakis
    return (None, None)
256 adc46059 Christos Stavrakakis
257 adc46059 Christos Stavrakakis
258 dca7553e Christos Stavrakakis
def get_public_ip(backend):
259 dca7553e Christos Stavrakakis
    """Reserve an IP from a public network.
260 dca7553e Christos Stavrakakis

261 dca7553e Christos Stavrakakis
    This method should run inside a transaction.
262 dca7553e Christos Stavrakakis

263 dca7553e Christos Stavrakakis
    """
264 dca7553e Christos Stavrakakis
    address = None
265 74459c12 Christos Stavrakakis
    if settings.PUBLIC_USE_POOL:
266 dca7553e Christos Stavrakakis
        (network, address) = allocate_public_address(backend)
267 dca7553e Christos Stavrakakis
    else:
268 dca7553e Christos Stavrakakis
        for net in list(backend_public_networks(backend)):
269 dca7553e Christos Stavrakakis
            pool = net.get_pool()
270 dca7553e Christos Stavrakakis
            if not pool.empty():
271 dca7553e Christos Stavrakakis
                address = 'pool'
272 dca7553e Christos Stavrakakis
                network = net
273 dca7553e Christos Stavrakakis
                break
274 dca7553e Christos Stavrakakis
    if address is None:
275 dca7553e Christos Stavrakakis
        log.error("Public networks of backend %s are full", backend)
276 dca7553e Christos Stavrakakis
        raise OverLimit("Can not allocate IP for new machine."
277 dca7553e Christos Stavrakakis
                        " Public networks are full.")
278 dca7553e Christos Stavrakakis
    return (network, address)
279 dca7553e Christos Stavrakakis
280 dca7553e Christos Stavrakakis
281 7fede91e Christos Stavrakakis
def backend_public_networks(backend):
282 7fede91e Christos Stavrakakis
    """Return available public networks of the backend.
283 7fede91e Christos Stavrakakis

284 7fede91e Christos Stavrakakis
    Iterator for non-deleted public networks that are available
285 7fede91e Christos Stavrakakis
    to the specified backend.
286 7fede91e Christos Stavrakakis

287 7fede91e Christos Stavrakakis
    """
288 7fede91e Christos Stavrakakis
    for network in Network.objects.filter(public=True, deleted=False):
289 7fede91e Christos Stavrakakis
        if BackendNetwork.objects.filter(network=network,
290 7fede91e Christos Stavrakakis
                                         backend=backend).exists():
291 7fede91e Christos Stavrakakis
            yield network
292 7fede91e Christos Stavrakakis
293 7fede91e Christos Stavrakakis
294 7fede91e Christos Stavrakakis
def get_network_free_address(network):
295 7fede91e Christos Stavrakakis
    """Reserve an IP address from the IP Pool of the network.
296 7fede91e Christos Stavrakakis

297 fdc94944 Christos Stavrakakis
    Raises EmptyPool
298 7fede91e Christos Stavrakakis

299 7fede91e Christos Stavrakakis
    """
300 7fede91e Christos Stavrakakis
301 fdc94944 Christos Stavrakakis
    pool = network.get_pool()
302 fdc94944 Christos Stavrakakis
    address = pool.get()
303 7fede91e Christos Stavrakakis
    pool.save()
304 7fede91e Christos Stavrakakis
    return address
305 7fede91e Christos Stavrakakis
306 7fede91e Christos Stavrakakis
307 d44c236b Giorgos Verigakis
def get_nic(machine, network):
308 d44c236b Giorgos Verigakis
    try:
309 d44c236b Giorgos Verigakis
        return NetworkInterface.objects.get(machine=machine, network=network)
310 d44c236b Giorgos Verigakis
    except NetworkInterface.DoesNotExist:
311 d44c236b Giorgos Verigakis
        raise ItemNotFound('Server not connected to this network.')
312 d44c236b Giorgos Verigakis
313 7fede91e Christos Stavrakakis
314 08b079e2 Stavros Sachtouris
def get_nic_from_index(vm, nic_index):
315 08b079e2 Stavros Sachtouris
    """Returns the nic_index-th nic of a vm
316 08b079e2 Stavros Sachtouris
       Error Response Codes: itemNotFound (404), badMediaType (415)
317 08b079e2 Stavros Sachtouris
    """
318 08b079e2 Stavros Sachtouris
    matching_nics = vm.nics.filter(index=nic_index)
319 08b079e2 Stavros Sachtouris
    matching_nics_len = len(matching_nics)
320 08b079e2 Stavros Sachtouris
    if matching_nics_len < 1:
321 e440e835 Christos Stavrakakis
        raise ItemNotFound('NIC not found on VM')
322 08b079e2 Stavros Sachtouris
    elif matching_nics_len > 1:
323 08b079e2 Stavros Sachtouris
        raise BadMediaType('NIC index conflict on VM')
324 08b079e2 Stavros Sachtouris
    nic = matching_nics[0]
325 08b079e2 Stavros Sachtouris
    return nic
326 e2ee7808 Giorgos Verigakis
327 7fede91e Christos Stavrakakis
328 7e2f9d4b Giorgos Verigakis
def get_request_dict(request):
329 d8e50a39 Giorgos Verigakis
    """Returns data sent by the client as a python dict."""
330 aa197ee4 Vangelis Koukis
331 7e2f9d4b Giorgos Verigakis
    data = request.raw_post_data
332 7e45ddef Dimitris Moraitis
    if request.META.get('CONTENT_TYPE').startswith('application/json'):
333 7e2f9d4b Giorgos Verigakis
        try:
334 7e2f9d4b Giorgos Verigakis
            return json.loads(data)
335 7e2f9d4b Giorgos Verigakis
        except ValueError:
336 d8e50a39 Giorgos Verigakis
            raise BadRequest('Invalid JSON data.')
337 d8e50a39 Giorgos Verigakis
    else:
338 d8e50a39 Giorgos Verigakis
        raise BadRequest('Unsupported Content-Type.')
339 7e2f9d4b Giorgos Verigakis
340 a08e4270 Vangelis Koukis
341 8b01f7f3 Giorgos Verigakis
def update_response_headers(request, response):
342 8b01f7f3 Giorgos Verigakis
    if request.serialization == 'xml':
343 8b01f7f3 Giorgos Verigakis
        response['Content-Type'] = 'application/xml'
344 8b01f7f3 Giorgos Verigakis
    elif request.serialization == 'atom':
345 8b01f7f3 Giorgos Verigakis
        response['Content-Type'] = 'application/atom+xml'
346 8b01f7f3 Giorgos Verigakis
    else:
347 8b01f7f3 Giorgos Verigakis
        response['Content-Type'] = 'application/json'
348 aa197ee4 Vangelis Koukis
349 4cf8adf8 Vangelis Koukis
    if settings.TEST:
350 cdb65551 Giorgos Verigakis
        response['Date'] = format_date_time(time())
351 ce55f211 Kostas Papadimitriou
352 b75555e5 Giorgos Verigakis
    add_never_cache_headers(response)
353 b75555e5 Giorgos Verigakis
354 8b01f7f3 Giorgos Verigakis
355 432fc8c3 Giorgos Verigakis
def render_metadata(request, metadata, use_values=False, status=200):
356 432fc8c3 Giorgos Verigakis
    if request.serialization == 'xml':
357 432fc8c3 Giorgos Verigakis
        data = render_to_string('metadata.xml', {'metadata': metadata})
358 432fc8c3 Giorgos Verigakis
    else:
359 b36f78fa Giorgos Verigakis
        if use_values:
360 b36f78fa Giorgos Verigakis
            d = {'metadata': {'values': metadata}}
361 b36f78fa Giorgos Verigakis
        else:
362 b36f78fa Giorgos Verigakis
            d = {'metadata': metadata}
363 432fc8c3 Giorgos Verigakis
        data = json.dumps(d)
364 432fc8c3 Giorgos Verigakis
    return HttpResponse(data, status=status)
365 432fc8c3 Giorgos Verigakis
366 a08e4270 Vangelis Koukis
367 432fc8c3 Giorgos Verigakis
def render_meta(request, meta, status=200):
368 432fc8c3 Giorgos Verigakis
    if request.serialization == 'xml':
369 6ef51e9f Giorgos Verigakis
        data = render_to_string('meta.xml', dict(key=key, val=val))
370 432fc8c3 Giorgos Verigakis
    else:
371 6ef51e9f Giorgos Verigakis
        data = json.dumps(dict(meta=meta))
372 432fc8c3 Giorgos Verigakis
    return HttpResponse(data, status=status)
373 432fc8c3 Giorgos Verigakis
374 a08e4270 Vangelis Koukis
375 c36934a7 Giorgos Verigakis
def render_fault(request, fault):
376 4cf8adf8 Vangelis Koukis
    if settings.DEBUG or settings.TEST:
377 7e2f9d4b Giorgos Verigakis
        fault.details = format_exc(fault)
378 aa197ee4 Vangelis Koukis
379 d8e50a39 Giorgos Verigakis
    if request.serialization == 'xml':
380 d8e50a39 Giorgos Verigakis
        data = render_to_string('fault.xml', {'fault': fault})
381 7e2f9d4b Giorgos Verigakis
    else:
382 e440e835 Christos Stavrakakis
        d = {fault.name: {'code': fault.code,
383 e440e835 Christos Stavrakakis
                          'message': fault.message,
384 e440e835 Christos Stavrakakis
                          'details': fault.details}}
385 7e2f9d4b Giorgos Verigakis
        data = json.dumps(d)
386 aa197ee4 Vangelis Koukis
387 f0656db1 Giorgos Verigakis
    resp = HttpResponse(data, status=fault.code)
388 8b01f7f3 Giorgos Verigakis
    update_response_headers(request, resp)
389 f0656db1 Giorgos Verigakis
    return resp
390 d8e50a39 Giorgos Verigakis
391 432fc8c3 Giorgos Verigakis
392 d8e50a39 Giorgos Verigakis
def request_serialization(request, atom_allowed=False):
393 d8e50a39 Giorgos Verigakis
    """Return the serialization format requested.
394 aa197ee4 Vangelis Koukis

395 529178b1 Giorgos Verigakis
    Valid formats are 'json', 'xml' and 'atom' if `atom_allowed` is True.
396 d8e50a39 Giorgos Verigakis
    """
397 aa197ee4 Vangelis Koukis
398 d8e50a39 Giorgos Verigakis
    path = request.path
399 aa197ee4 Vangelis Koukis
400 d8e50a39 Giorgos Verigakis
    if path.endswith('.json'):
401 d8e50a39 Giorgos Verigakis
        return 'json'
402 d8e50a39 Giorgos Verigakis
    elif path.endswith('.xml'):
403 d8e50a39 Giorgos Verigakis
        return 'xml'
404 d8e50a39 Giorgos Verigakis
    elif atom_allowed and path.endswith('.atom'):
405 d8e50a39 Giorgos Verigakis
        return 'atom'
406 aa197ee4 Vangelis Koukis
407 d8e50a39 Giorgos Verigakis
    for item in request.META.get('HTTP_ACCEPT', '').split(','):
408 d8e50a39 Giorgos Verigakis
        accept, sep, rest = item.strip().partition(';')
409 d8e50a39 Giorgos Verigakis
        if accept == 'application/json':
410 d8e50a39 Giorgos Verigakis
            return 'json'
411 d8e50a39 Giorgos Verigakis
        elif accept == 'application/xml':
412 d8e50a39 Giorgos Verigakis
            return 'xml'
413 d8e50a39 Giorgos Verigakis
        elif atom_allowed and accept == 'application/atom+xml':
414 d8e50a39 Giorgos Verigakis
            return 'atom'
415 aa197ee4 Vangelis Koukis
416 d8e50a39 Giorgos Verigakis
    return 'json'
417 7e2f9d4b Giorgos Verigakis
418 a08e4270 Vangelis Koukis
419 d8e50a39 Giorgos Verigakis
def api_method(http_method=None, atom_allowed=False):
420 d8e50a39 Giorgos Verigakis
    """Decorator function for views that implement an API method."""
421 aa197ee4 Vangelis Koukis
422 c36934a7 Giorgos Verigakis
    def decorator(func):
423 c36934a7 Giorgos Verigakis
        @wraps(func)
424 c36934a7 Giorgos Verigakis
        def wrapper(request, *args, **kwargs):
425 c36934a7 Giorgos Verigakis
            try:
426 6ef51e9f Giorgos Verigakis
                request.serialization = request_serialization(request,
427 6ef51e9f Giorgos Verigakis
                                                              atom_allowed)
428 4b3b8688 Giorgos Verigakis
                get_user(request, settings.ASTAKOS_URL)
429 4b3b8688 Giorgos Verigakis
                if not request.user_uniq:
430 40777cc8 Giorgos Verigakis
                    raise Unauthorized('No user found.')
431 d8e50a39 Giorgos Verigakis
                if http_method and request.method != http_method:
432 d8e50a39 Giorgos Verigakis
                    raise BadRequest('Method not allowed.')
433 7fede91e Christos Stavrakakis
434 c36934a7 Giorgos Verigakis
                resp = func(request, *args, **kwargs)
435 8b01f7f3 Giorgos Verigakis
                update_response_headers(request, resp)
436 c36934a7 Giorgos Verigakis
                return resp
437 5231a38a Giorgos Verigakis
            except VirtualMachine.DeletedError:
438 5231a38a Giorgos Verigakis
                fault = BadRequest('Server has been deleted.')
439 5231a38a Giorgos Verigakis
                return render_fault(request, fault)
440 22a9959d Christos Stavrakakis
            except Network.DeletedError:
441 22a9959d Christos Stavrakakis
                fault = BadRequest('Network has been deleted.')
442 22a9959d Christos Stavrakakis
                return render_fault(request, fault)
443 5231a38a Giorgos Verigakis
            except VirtualMachine.BuildingError:
444 5231a38a Giorgos Verigakis
                fault = BuildInProgress('Server is being built.')
445 5231a38a Giorgos Verigakis
                return render_fault(request, fault)
446 2db7d9df Christos Stavrakakis
            except NotAllowedError:
447 2db7d9df Christos Stavrakakis
                # Image Backend Unathorized
448 2db7d9df Christos Stavrakakis
                fault = Forbidden('Request not allowed.')
449 2db7d9df Christos Stavrakakis
                return render_fault(request, fault)
450 c36934a7 Giorgos Verigakis
            except Fault, fault:
451 260147b1 Christos Stavrakakis
                if fault.code >= 500:
452 260147b1 Christos Stavrakakis
                    log.exception('API fault')
453 c36934a7 Giorgos Verigakis
                return render_fault(request, fault)
454 7fede91e Christos Stavrakakis
            except BaseException:
455 9e98ba3c Giorgos Verigakis
                log.exception('Unexpected error')
456 529178b1 Giorgos Verigakis
                fault = ServiceUnavailable('Unexpected error.')
457 c36934a7 Giorgos Verigakis
                return render_fault(request, fault)
458 c36934a7 Giorgos Verigakis
        return wrapper
459 c36934a7 Giorgos Verigakis
    return decorator
460 08b079e2 Stavros Sachtouris
461 3165f027 Christos Stavrakakis
462 08b079e2 Stavros Sachtouris
def construct_nic_id(nic):
463 08b079e2 Stavros Sachtouris
    return "-".join(["nic", unicode(nic.machine.id), unicode(nic.index)])
464 6dd70a5c Christos Stavrakakis
465 6dd70a5c Christos Stavrakakis
466 dca7553e Christos Stavrakakis
def verify_personality(personality):
467 6ec4694f Christos Stavrakakis
    """Verify that a a list of personalities is well formed"""
468 006c6249 Christos Stavrakakis
    if len(personality) > settings.MAX_PERSONALITY:
469 6ec4694f Christos Stavrakakis
        raise OverLimit("Maximum number of personalities"
470 e440e835 Christos Stavrakakis
                        " exceeded")
471 dca7553e Christos Stavrakakis
    for p in personality:
472 dca7553e Christos Stavrakakis
        # Verify that personalities are well-formed
473 dca7553e Christos Stavrakakis
        try:
474 dca7553e Christos Stavrakakis
            assert isinstance(p, dict)
475 dca7553e Christos Stavrakakis
            keys = set(p.keys())
476 dca7553e Christos Stavrakakis
            allowed = set(['contents', 'group', 'mode', 'owner', 'path'])
477 dca7553e Christos Stavrakakis
            assert keys.issubset(allowed)
478 dca7553e Christos Stavrakakis
            contents = p['contents']
479 dca7553e Christos Stavrakakis
            if len(contents) > settings.MAX_PERSONALITY_SIZE:
480 dca7553e Christos Stavrakakis
                # No need to decode if contents already exceed limit
481 dca7553e Christos Stavrakakis
                raise OverLimit("Maximum size of personality exceeded")
482 dca7553e Christos Stavrakakis
            if len(b64decode(contents)) > settings.MAX_PERSONALITY_SIZE:
483 dca7553e Christos Stavrakakis
                raise OverLimit("Maximum size of personality exceeded")
484 dca7553e Christos Stavrakakis
        except AssertionError:
485 dca7553e Christos Stavrakakis
            raise BadRequest("Malformed personality in request")
486 2a599282 Christos Stavrakakis
487 2a599282 Christos Stavrakakis
488 2a599282 Christos Stavrakakis
def get_flavor_provider(flavor):
489 2a599282 Christos Stavrakakis
    """Extract provider from disk template.
490 2a599282 Christos Stavrakakis

491 2a599282 Christos Stavrakakis
    Provider for `ext` disk_template is encoded in the disk template
492 2a599282 Christos Stavrakakis
    name, which is formed `ext_<provider_name>`. Provider is None
493 2a599282 Christos Stavrakakis
    for all other disk templates.
494 2a599282 Christos Stavrakakis

495 2a599282 Christos Stavrakakis
    """
496 2a599282 Christos Stavrakakis
    disk_template = flavor.disk_template
497 2a599282 Christos Stavrakakis
    provider = None
498 2a599282 Christos Stavrakakis
    if disk_template.startswith("ext"):
499 2a599282 Christos Stavrakakis
        disk_template, provider = disk_template.split("_", 1)
500 2a599282 Christos Stavrakakis
    return disk_template, provider
501 b7d38981 Dimitris Aragiorgis
502 e440e835 Christos Stavrakakis
503 b7d38981 Dimitris Aragiorgis
def values_from_flavor(flavor):
504 b7d38981 Dimitris Aragiorgis
    """Get Ganeti connectivity info from flavor type.
505 b7d38981 Dimitris Aragiorgis

506 b7d38981 Dimitris Aragiorgis
    If link or mac_prefix equals to "pool", then the resources
507 b7d38981 Dimitris Aragiorgis
    are allocated from the corresponding Pools.
508 b7d38981 Dimitris Aragiorgis

509 b7d38981 Dimitris Aragiorgis
    """
510 b7d38981 Dimitris Aragiorgis
    try:
511 b7d38981 Dimitris Aragiorgis
        flavor = Network.FLAVORS[flavor]
512 b7d38981 Dimitris Aragiorgis
    except KeyError:
513 b7d38981 Dimitris Aragiorgis
        raise BadRequest("Unknown network flavor")
514 b7d38981 Dimitris Aragiorgis
515 b7d38981 Dimitris Aragiorgis
    mode = flavor.get("mode")
516 b7d38981 Dimitris Aragiorgis
517 b7d38981 Dimitris Aragiorgis
    link = flavor.get("link")
518 b7d38981 Dimitris Aragiorgis
    if link == "pool":
519 b7d38981 Dimitris Aragiorgis
        link = allocate_resource("bridge")
520 b7d38981 Dimitris Aragiorgis
521 b7d38981 Dimitris Aragiorgis
    mac_prefix = flavor.get("mac_prefix")
522 b7d38981 Dimitris Aragiorgis
    if mac_prefix == "pool":
523 b7d38981 Dimitris Aragiorgis
        mac_prefix = allocate_resource("mac_prefix")
524 b7d38981 Dimitris Aragiorgis
525 b7d38981 Dimitris Aragiorgis
    tags = flavor.get("tags")
526 b7d38981 Dimitris Aragiorgis
527 b7d38981 Dimitris Aragiorgis
    return mode, link, mac_prefix, tags
528 b7d38981 Dimitris Aragiorgis
529 b7d38981 Dimitris Aragiorgis
530 b7d38981 Dimitris Aragiorgis
def allocate_resource(res_type):
531 b7d38981 Dimitris Aragiorgis
    table = get_pool_table(res_type)
532 b7d38981 Dimitris Aragiorgis
    pool = table.get_pool()
533 b7d38981 Dimitris Aragiorgis
    value = pool.get()
534 b7d38981 Dimitris Aragiorgis
    pool.save()
535 b7d38981 Dimitris Aragiorgis
    return value
536 b7d38981 Dimitris Aragiorgis
537 b7d38981 Dimitris Aragiorgis
538 b7d38981 Dimitris Aragiorgis
def release_resource(res_type, value):
539 b7d38981 Dimitris Aragiorgis
    table = get_pool_table(res_type)
540 b7d38981 Dimitris Aragiorgis
    pool = table.get_pool()
541 b7d38981 Dimitris Aragiorgis
    pool.put(value)
542 b7d38981 Dimitris Aragiorgis
    pool.save()
543 b7d38981 Dimitris Aragiorgis
544 b7d38981 Dimitris Aragiorgis
545 b7d38981 Dimitris Aragiorgis
def get_pool_table(res_type):
546 b7d38981 Dimitris Aragiorgis
    if res_type == "bridge":
547 b7d38981 Dimitris Aragiorgis
        return BridgePoolTable
548 b7d38981 Dimitris Aragiorgis
    elif res_type == "mac_prefix":
549 b7d38981 Dimitris Aragiorgis
        return MacPrefixPoolTable
550 b7d38981 Dimitris Aragiorgis
    else:
551 b7d38981 Dimitris Aragiorgis
        raise Exception("Unknown resource type")
552 55f562a4 Kostas Papadimitriou
553 55f562a4 Kostas Papadimitriou
554 55f562a4 Kostas Papadimitriou
def get_existing_users():
555 55f562a4 Kostas Papadimitriou
    """
556 55f562a4 Kostas Papadimitriou
    Retrieve user ids stored in cyclades user agnostic models.
557 55f562a4 Kostas Papadimitriou
    """
558 55f562a4 Kostas Papadimitriou
    # also check PublicKeys a user with no servers/networks exist
559 55f562a4 Kostas Papadimitriou
    from synnefo.ui.userdata.models import PublicKeyPair
560 55f562a4 Kostas Papadimitriou
    from synnefo.db.models import VirtualMachine, Network
561 55f562a4 Kostas Papadimitriou
562 55f562a4 Kostas Papadimitriou
    keypairusernames = PublicKeyPair.objects.filter().values_list('user',
563 e440e835 Christos Stavrakakis
                                                                  flat=True)
564 55f562a4 Kostas Papadimitriou
    serverusernames = VirtualMachine.objects.filter().values_list('userid',
565 e440e835 Christos Stavrakakis
                                                                  flat=True)
566 55f562a4 Kostas Papadimitriou
    networkusernames = Network.objects.filter().values_list('userid',
567 55f562a4 Kostas Papadimitriou
                                                            flat=True)
568 55f562a4 Kostas Papadimitriou
569 e440e835 Christos Stavrakakis
    return set(list(keypairusernames) + list(serverusernames) +
570 e440e835 Christos Stavrakakis
               list(networkusernames))