Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / subnets.py @ bf58eed3

History | View | Annotate | Download (9.4 kB)

1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from logging import getLogger
35
from snf_django.lib import api
36

    
37
from django.conf.urls import patterns
38
from django.http import HttpResponse
39
from django.utils import simplejson as json
40
from django.db.models import Q
41

    
42
from snf_django.lib.api import utils
43
from synnefo.db.models import Subnet
44
from synnefo.logic import subnets
45
from synnefo.api import util
46

    
47
import ipaddr
48

    
49
log = getLogger(__name__)
50

    
51

    
52
urlpatterns = patterns(
53
    'synnefo.api.subnets',
54
    (r'^(?:/|.json|.xml)?$', 'demux'),
55
    (r'^/detail(?:.json|.xml)?$', 'list_subnets', {'detail': True}),
56
    (r'^/([-\w]+)(?:/|.json|.xml)?$', 'subnet_demux'))
57

    
58

    
59
def demux(request):
60
    if request.method == 'GET':
61
        return list_subnets(request)
62
    elif request.method == 'POST':
63
        return create_subnet(request)
64
    else:
65
        return api.api_method_not_allowed(request)
66

    
67

    
68
def subnet_demux(request, sub_id):
69
    if request.method == 'GET':
70
        return get_subnet(request, sub_id)
71
    elif request.method == 'DELETE':
72
        return delete_subnet(request, sub_id)
73
    elif request.method == 'PUT':
74
        return update_subnet(request, sub_id)
75
    else:
76
        return api.api_method_not_allowed(request)
77

    
78

    
79
@api.api_method(http_method='GET', user_required=True, logger=log)
80
def list_subnets(request, detail=True):
81
    """List all subnets of a user"""
82
    userid = request.user_uniq
83
    subnets_list = Subnet.objects.filter(Q(network__public=True) |
84
                                         (Q(network__userid=userid) &
85
                                          Q(network__public=False)))\
86
                                 .order_by("id")
87
    subnets_list = subnets_list.prefetch_related("ip_pools")\
88
                               .select_related("network")
89
    subnets_list = api.utils.filter_modified_since(request,
90
                                                   objects=subnets_list)
91

    
92
    subnets_dict = [subnet_to_dict(sub) for sub in subnets_list]
93

    
94
    data = json.dumps({'subnets': subnets_dict})
95

    
96
    return HttpResponse(data, status=200)
97

    
98

    
99
@api.api_method(http_method='POST', user_required=True, logger=log)
100
def create_subnet(request):
101
    """Create a subnet
102
    network_id and the desired cidr are mandatory, everything else is optional
103

104
    """
105
    dictionary = utils.get_request_dict(request)
106
    log.info('create subnet %s', dictionary)
107

    
108
    try:
109
        subnet = dictionary['subnet']
110
        network_id = subnet['network_id']
111
        cidr = subnet['cidr']
112
    except KeyError:
113
        raise api.faults.BadRequest("Malformed request")
114

    
115
    name = subnet.get('name', None)
116
    ipversion = subnet.get('ip_version', 4)
117

    
118
    allocation_pools = subnet.get('allocation_pools', None)
119
    if allocation_pools is not None:
120
        allocation_pools = parse_ip_pools(allocation_pools)
121

    
122
    try:
123
        cidr_ip = ipaddr.IPNetwork(cidr)
124
    except ValueError:
125
        raise api.faults.BadRequest("Malformed CIDR '%s'" % cidr)
126

    
127
    # If no gateway is specified, send an empty string, because None is used
128
    # if the user wants no gateway at all
129
    gateway = subnet.get('gateway_ip', "")
130
    if gateway is "":
131
        gateway = str(cidr_ip.network + 1)
132

    
133
    dhcp = subnet.get('enable_dhcp', True)
134
    slaac = subnet.get('enable_slaac', None)
135

    
136
    if ipversion == 6:
137
        if slaac is not None:
138
            dhcp = check_boolean_value(slaac, "enable_slaac")
139
        else:
140
            dhcp = check_boolean_value(dhcp, "dhcp")
141
    else:
142
        dhcp = check_boolean_value(dhcp, "dhcp")
143

    
144
    dns = subnet.get('dns_nameservers', None)
145
    hosts = subnet.get('host_routes', None)
146

    
147
    sub = subnets.create_subnet(network_id=network_id,
148
                                cidr=cidr,
149
                                name=name,
150
                                ipversion=ipversion,
151
                                gateway=gateway,
152
                                dhcp=dhcp,
153
                                slaac=slaac,
154
                                dns_nameservers=dns,
155
                                allocation_pools=allocation_pools,
156
                                host_routes=hosts,
157
                                user_id=request.user_uniq)
158

    
159
    subnet_dict = subnet_to_dict(sub)
160
    data = json.dumps({'subnet': subnet_dict})
161
    return HttpResponse(data, status=201)
162

    
163

    
164
@api.api_method(http_method='GET', user_required=True, logger=log)
165
def get_subnet(request, sub_id):
166
    """Show info of a specific subnet"""
167
    user_id = request.user_uniq
168
    subnet = subnets.get_subnet(sub_id)
169

    
170
    if (subnet.network.userid != user_id) and (subnet.network.public is False):
171
        raise api.faults.Unauthorized("You're not allowed to view this subnet")
172

    
173
    subnet_dict = subnet_to_dict(subnet)
174
    data = json.dumps({'subnet': subnet_dict})
175
    return HttpResponse(data, status=200)
176

    
177

    
178
@api.api_method(http_method='DELETE', user_required=True, logger=log)
179
def delete_subnet(request, sub_id):
180
    """Delete a subnet, raises BadRequest
181
    A subnet is deleted ONLY when the network that it belongs to is deleted
182

183
    """
184
    raise api.faults.BadRequest("Deletion of a subnet is not supported")
185

    
186

    
187
@api.api_method(http_method='PUT', user_required=True, logger=log)
188
def update_subnet(request, sub_id):
189
    """Update the fields of a subnet
190
    Only the name can be updated, everything else returns BadRequest
191

192
    """
193

    
194
    dictionary = utils.get_request_dict(request)
195
    user_id = request.user_uniq
196

    
197
    try:
198
        subnet = dictionary['subnet']
199
    except KeyError:
200
        raise api.faults.BadRequest("Malformed request")
201

    
202
    if len(subnet) != 1 or "name" not in subnet:
203
        raise api.faults.BadRequest("Only the name of subnet can be updated")
204

    
205
    name = subnet.get("name", None)
206

    
207
    subnet_dict = subnet_to_dict(subnets.update_subnet(sub_id, name, user_id))
208
    data = json.dumps({'subnet': subnet_dict})
209
    return HttpResponse(data, status=200)
210

    
211

    
212
#Utility functions
213
def subnet_to_dict(subnet):
214
    """Returns a dictionary containing the info of a subnet"""
215
    dns = check_empty_lists(subnet.dns_nameservers)
216
    hosts = check_empty_lists(subnet.host_routes)
217

    
218
    allocation_pools = [render_ip_pool(pool)
219
                        for pool in subnet.ip_pools.all()]
220

    
221
    network = subnet.network
222
    d = {'id': str(subnet.id),
223
         'network_id': str(subnet.network_id),
224
         'name': subnet.name if subnet.name is not None else "",
225
         'tenant_id': network.userid,
226
         'user_id': network.userid,
227
         'gateway_ip': subnet.gateway,
228
         'ip_version': subnet.ipversion,
229
         'cidr': subnet.cidr,
230
         'enable_dhcp': subnet.dhcp,
231
         'dns_nameservers': dns,
232
         'host_routes': hosts,
233
         'allocation_pools': allocation_pools}
234

    
235
    if subnet.ipversion == 6:
236
        d['enable_slaac'] = subnet.dhcp
237

    
238
    d['links'] = util.subnet_to_links(subnet.id)
239

    
240
    return d
241

    
242

    
243
def render_ip_pool(pool):
244
    network = ipaddr.IPNetwork(pool.base).network
245
    start = str(network + pool.offset)
246
    end = str(network + pool.offset + pool.size - 1)
247
    return {"start": start, "end": end}
248

    
249

    
250
def parse_ip_pools(pools):
251
    """Convert [{'start': '192.168.42.1', 'end': '192.168.42.15'},
252
             {'start': '192.168.42.30', 'end': '192.168.42.60'}]
253
    to
254
            [(IPv4Address("192.168.42.1"), IPv4Address("192.168.42.15")),
255
             (IPv4Address("192.168.42.30"), IPv4Address("192.168.42.60"))]
256

257
    """
258
    try:
259
        return sorted([(ipaddr.IPv4Address(p["start"]),
260
                        ipaddr.IPv4Address(p["end"])) for p in pools])
261
    except KeyError:
262
        raise api.faults.BadRequest("Malformed allocation pool.")
263
    except ipaddr.AddressValueError:
264
        raise api.faults.BadRequest("Allocation pools contain invalid IPv4"
265
                                    " address")
266

    
267

    
268
def check_empty_lists(value):
269
    """Check if value is Null/None, in which case we return an empty list"""
270
    if value is None:
271
        return []
272
    return value
273

    
274

    
275
def check_boolean_value(value, key):
276
    """Check if dhcp value is in acceptable values"""
277
    if value not in [True, False]:
278
        raise api.faults.BadRequest("Malformed request, %s must "
279
                                    "be True or False" % key)
280
    return value