Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (13 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
from snf_django.lib.api import faults
37

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

    
42
from snf_django.lib.api import utils
43
from synnefo.db.models import Subnet, Network
44
from synnefo.logic import networks
45

    
46
from ipaddr import IPv4Network, IPv6Network, IPv4Address, IPAddress, IPNetwork
47

    
48
log = getLogger(__name__)
49

    
50

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

    
56

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

    
65

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

    
76

    
77
@api.api_method(http_method='GET', user_required=True, logger=log)
78
def list_subnets(request):
79
    """List all subnets of a user"""
80
    log.debug('list_subnets')
81

    
82
    user_subnets = Subnet.objects.filter(network__userid=request.user_uniq)
83
    subnets_dict = [subnet_to_dict(sub)
84
                    for sub in user_subnets.order_by('id')]
85
    data = json.dumps({'subnets': subnets_dict})
86

    
87
    return HttpResponse(data, status=200)
88

    
89

    
90
@api.api_method(http_method='POST', user_required=True, logger=log)
91
def create_subnet(request):
92
    """
93
    Create a subnet
94
    network_id and the desired cidr are mandatory, everything else is optional
95
    """
96

    
97
    dictionary = utils.get_request_dict(request)
98
    log.info('create subnet %s', dictionary)
99
    user_id = request.user_uniq
100

    
101
    try:
102
        subnet = dictionary['subnet']
103
        network_id = subnet['network_id']
104
        cidr = subnet['cidr']
105
    except KeyError:
106
        raise api.faults.BadRequest("Malformed request")
107

    
108
    try:
109
        network = Network.objects.get(id=network_id)
110
    except Network.DoesNotExist:
111
        raise api.faults.ItemNotFound("No networks found with that id")
112

    
113
    if user_id != network.userid:
114
        raise api.faults.Unauthorized("Unauthorized operation")
115

    
116
    ipversion = subnet.get('ip_version', 4)
117
    if ipversion not in [4, 6]:
118
        raise api.faults.BadRequest("Malformed IP version type")
119

    
120
    # Returns the first available IP in the subnet
121
    if ipversion == 6:
122
        potential_gateway = str(IPv6Network(cidr).network + 1)
123
        check_number_of_subnets(network, 6)
124
    else:
125
        potential_gateway = str(IPv4Network(cidr).network + 1)
126
        check_number_of_subnets(network, 4)
127

    
128
    gateway = subnet.get('gateway_ip', potential_gateway)
129

    
130
    if ipversion == 6:
131
        networks.validate_network_params(None, None, cidr, gateway)
132
        slac = subnet.get('slac', None)
133
        if slac is not None:
134
            dhcp = check_dhcp_value(slac)
135
        else:
136
            dhcp = check_dhcp_value(subnet.get('enable_dhcp', True))
137
    else:
138
        networks.validate_network_params(cidr, gateway)
139
        dhcp = check_dhcp_value(subnet.get('enable_dhcp', True))
140

    
141
    name = check_name_length(subnet.get('name', None))
142

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

    
146
    gateway_ip = IPAddress(gateway)
147
    cidr_ip = IPNetwork(cidr)
148

    
149
    allocation_pools = subnet.get('allocation_pools', None)
150

    
151
    if allocation_pools:
152
        if ipversion == 6:
153
            raise api.faults.Conflict("Can't allocate an IP Pool in IPv6")
154
        pools = parse_ip_pools(allocation_pools)
155
        validate_subpools(pools, cidr_ip, gateway_ip)
156
    else:
157
        # FIX ME
158
        pass
159

    
160
    # FIX ME
161
    try:
162
        sub = Subnet.objects.create(name=name, network=network, cidr=cidr,
163
                                    ipversion=ipversion, gateway=gateway,
164
                                    dhcp=dhcp, host_routes=hosts,
165
                                    dns_nameservers=dns)
166
    except:
167
        raise
168
        return "Error"
169

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

    
174

    
175
@api.api_method(http_method='GET', user_required=True, logger=log)
176
def get_subnet(request, sub_id):
177
    """Show info of a specific subnet"""
178
    log.debug('get_subnet %s', sub_id)
179
    user_id = request.user_uniq
180

    
181
    try:
182
        subnet = Subnet.objects.get(id=sub_id)
183
    except Subnet.DoesNotExist:
184
        raise api.faults.ItemNotFound("Subnet not found")
185

    
186
    if subnet.network.userid != user_id:
187
        raise api.failts.Unauthorized("You're not allowed to view this subnet")
188

    
189
    subnet_dict = subnet_to_dict(subnet)
190
    data = json.dumps({'subnet': subnet_dict})
191
    return HttpResponse(data, status=200)
192

    
193

    
194
@api.api_method(http_method='DELETE', user_required=True, logger=log)
195
def delete_subnet(request, sub_id):
196
    """
197
    Delete a subnet, raises BadRequest
198
    A subnet is deleted ONLY when the network that it belongs to is deleted
199
    """
200
    raise api.faults.BadRequest("Deletion of a subnet is not supported")
201

    
202

    
203
@api.api_method(http_method='PUT', user_required=True, logger=log)
204
def update_subnet(request, sub_id):
205
    """
206
    Update the fields of a subnet
207
    Only the name can be updated, everything else returns BadRequest
208
    """
209

    
210
    dictionary = utils.get_request_dict(request)
211
    log.info('Update subnet %s', dictionary)
212
    user_id = request.user_uniq
213

    
214
    try:
215
        subnet = dictionary['subnet']
216
    except KeyError:
217
        raise api.faults.BadRequest("Malformed request")
218

    
219
    original_subnet = get_subnet_fromdb(sub_id, user_id)
220
    original_dict = subnet_to_dict(original_subnet)
221

    
222
    if len(subnet) != 1:
223
        raise api.faults.BadRequest("Only the name of subnet can be updated")
224

    
225
    name = subnet.get("name", None)
226

    
227
    if not name:
228
        raise api.faults.BadRequest("Only the name of subnet can be updated")
229

    
230
    #if subnet.get('ip_version', None):
231
    #    raise api.faults.BadRequest("Malformed request, ip_version cannot be "
232
    #                                "updated")
233
    #if subnet.get('cidr', None):
234
    #    raise api.faults.BadRequest("Malformed request, cidr cannot be "
235
    #                                "updated")
236
    #if subnet.get('allocation_pools', None):
237
    #    raise api.faults.BadRequest("Malformed request, allocation pools "
238
    #                                "cannot be updated")
239
    #
240
    # Check if request contained host/dns information
241
    #check_for_hosts_dns(subnet)
242
    #
243
    #name = subnet.get('name', original_dict['name'])
244
    check_name_length(name)
245

    
246
    #dhcp = subnet.get('enable_dhcp', original_dict['enable_dhcp'])
247
    #check_dhcp_value(dhcp)
248
    #
249
    #gateway = subnet.get('gateway_ip', original_dict['gateway_ip'])
250
    #FIX ME, check if IP is in use
251
    #if original_dict['ip_version'] == 6:
252
    #    networks.validate_network_params(None, None, original_dict['cidr'],
253
    #                                     gateway)
254
    #else:
255
    #    networks.validate_network_params(original_dict['cidr'], gateway)
256
    #
257
    try:
258
        #original_subnet.gateway = gateway
259
        original_subnet.name = name
260
        #original_subnet.dhcp = dhcp
261
        original_subnet.save()
262
    except:
263
        #Fix me
264
        return "Unknown Error"
265

    
266
    subnet_dict = subnet_to_dict(original_subnet)
267
    data = json.dumps({'subnet': subnet_dict})
268
    return HttpResponse(data, status=200)
269

    
270

    
271
#Utility functions
272
def subnet_to_dict(subnet):
273
    """Returns a dictionary containing the info of a subnet"""
274
    # FIX ME, allocation pools
275
    dictionary = dict({'id': subnet.id, 'network_id': subnet.network.id,
276
                       'name': subnet.name, 'tenant_id': subnet.network.userid,
277
                       'gateway_ip': subnet.gateway,
278
                       'ip_version': subnet.ipversion, 'cidr': subnet.cidr,
279
                       'enable_dhcp': subnet.dhcp,
280
                       'dns_nameservers': subnet.dns_nameservers,
281
                       'host_routes': subnet.host_routes,
282
                       'allocation_pools': []})
283

    
284
    if subnet.ipversion == 6:
285
        dictionary['slac'] = subnet.dhcp
286

    
287
    return dictionary
288

    
289

    
290
def check_number_of_subnets(network, version):
291
    """Check if a user can add a subnet in a network"""
292
    if network.subnets.filter(ipversion=version):
293
        raise api.faults.BadRequest("Only one subnet of IPv4/IPv6 per "
294
                                    "network is allowed")
295

    
296

    
297
def check_dhcp_value(dhcp):
298
    """Check if dhcp value is in acceptable values"""
299
    if dhcp not in [True, False]:
300
        raise api.faults.BadRequest("Malformed request, enable_dhcp/slac must "
301
                                    "be True or False")
302
    return dhcp
303

    
304

    
305
def check_name_length(name):
306
    """Check if the length of a name is within acceptable value"""
307
    if len(str(name)) > Subnet.SUBNET_NAME_LENGTH:
308
        raise api.faults.BadRequest("Subnet name too long")
309
    return name
310

    
311

    
312
def check_for_hosts_dns(subnet):
313
    """
314
    Check if a request contains host_routes or dns_nameservers options
315
    Expects the request in a dictionary format
316
    """
317
    if subnet.get('host_routes', None):
318
        raise api.faults.BadRequest("Setting host routes isn't supported")
319
    if subnet.get('dns_nameservers', None):
320
        raise api.faults.BadRequest("Setting dns nameservers isn't supported")
321

    
322

    
323
def get_subnet_fromdb(subnet_id, user_id, for_update=False):
324
    """
325
    Return a Subnet instance or raise ItemNotFound.
326
    This is the same as util.get_network
327
    """
328
    try:
329
        subnet_id = int(subnet_id)
330
        if for_update:
331
            return Subnet.objects.select_for_update().get(id=subnet_id,
332
                                                          network__userid=
333
                                                          user_id)
334
        return Subnet.objects.get(id=subnet_id, network__userid=user_id)
335
    except (ValueError, Subnet.DoesNotExist):
336
        raise api.faults.ItemNotFound('Subnet not found.')
337

    
338

    
339
def parse_ip_pools(pools):
340
    """
341
    Convert [{'start': '192.168.42.1', 'end': '192.168.42.15'},
342
             {'start': '192.168.42.30', 'end': '192.168.42.60'}]
343
    to
344
            [["192.168.42.1", "192.168.42.15"],
345
             ["192.168.42.30", "192.168.42.60"]]
346
    """
347
    pool_list = list()
348
    for pool in pools:
349
        asd = [pool["start"], pool["end"]]
350
        pool_list.append(asd)
351
    return pool_list
352

    
353

    
354
def validate_subpools(pools, cidr, gateway):
355
    """
356
    Validate the given IP pools are inside the cidr range
357
    Validate there are no overlaps in the given pools
358
    Input must be a list containing a sublist with start/end ranges as strings
359
    [["192.168.42.1", "192.168.42.15"], ["192.168.42.30", "192.168.42.60"]]
360
    """
361
    pool_list = list()
362
    for pool in pools:
363
        pool_list.append(map(lambda a: IPAddress(a), pool))
364
    pool_list = sorted(pool_list)
365

    
366
    if pool_list[0][0] <= cidr.network:
367
        raise api.faults.Conflict("IP Pool out of bounds")
368
    elif pool_list[-1][1] >= cidr.broadcast:
369
        raise api.faults.Conflict("IP Pool out of bounds")
370

    
371
    for start, end in pool_list:
372
        if start >= end:
373
            raise api.faults.Conflict("Invalid IP pool range")
374
        # Raise BadRequest if gateway is inside the pool range
375
        if not (gateway < start or gateway > end):
376
            raise api.faults.Conflict("Gateway cannot be in pool range")
377

    
378
    # Check if there is a conflict between the IP Poll ranges
379
    end = cidr.network
380
    for pool in pool_list:
381
        if end >= pool[1]:
382
            raise api.faults.Conflict("IP Pool range conflict")
383
        end = pool[1]