Statistics
| Branch: | Tag: | Revision:

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

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'^/([-\w]+)(?:/|.json|.xml)?$', 'subnet_demux'))
56

    
57

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

    
66

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

    
77

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

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

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

    
95
    return HttpResponse(data, status=200)
96

    
97

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

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

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

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

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

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

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

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

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

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

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

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

    
162

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

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

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

    
176

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

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

    
185

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

191
    """
192

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

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

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

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

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

    
210

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

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

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

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

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

    
239
    return d
240

    
241

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

    
248

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

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

    
266

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

    
273

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