Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (11.3 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
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
    """Create a subnet"""
93

    
94
    dictionary = utils.get_request_dict(request)
95
    log.info('create subnet %s', dictionary)
96
    user_id = request.user_uniq
97

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

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

    
110
    if user_id != network.userid:
111
        raise api.faults.Unauthorized("Unauthorized operation")
112

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

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

    
125
    gateway = subnet.get('gateway_ip', potential_gateway)
126

    
127
    if ipversion == 6:
128
        networks.validate_network_params(None, None, cidr, gateway)
129
    else:
130
        networks.validate_network_params(cidr, gateway)
131

    
132
    allocation_pools = subnet.get('allocation_pools', None)
133

    
134
    if allocation_pools:
135
        if ipversion == 6:
136
            raise api.faults.BadRequest("Can't allocate an IP Pool in IPv6")
137
        for pool in allocation_pools:
138
            start = pool['start']
139
            end = pool['end']
140
            networks.validate_network_params(cidr, start)
141
            networks.validate_network_params(cidr, end)
142
            start = IPv4Address(start)
143
            end = IPv4Address(end)
144
            if start >= end:
145
                raise api.faults.BadRequest("Invalid IP pool range")
146
    else:
147
        # FIX ME
148
        pass
149

    
150
    dhcp = check_dhcp_value(subnet.get('enable_dhcp', True))
151
    name = check_name_length(subnet.get('name', None))
152

    
153
    dns = subnet.get('dns_nameservers', None)
154
    hosts = subnet.get('host_routes', None)
155

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

    
166
    subnet_dict = subnet_to_dict(sub)
167
    data = json.dumps({'subnet': subnet_dict})
168
    return HttpResponse(data, status=200)
169

    
170

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

    
177
    try:
178
        subnet = Subnet.objects.get(id=sub_id)
179
    except Subnet.DoesNotExist:
180
        raise api.faults.ItemNotFound("Subnet not found")
181

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

    
185
    subnet_dict = subnet_to_dict(subnet)
186
    data = json.dumps({'subnet': subnet_dict})
187
    return HttpResponse(data, status=200)
188

    
189

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

    
198

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

    
206
    dictionary = utils.get_request_dict(request)
207
    log.info('Update subnet %s', dictionary)
208
    user_id = request.user_uniq
209

    
210
    try:
211
        subnet = dictionary['subnet']
212
    except KeyError:
213
        raise api.faults.BadRequest("Malformed request")
214

    
215
    original_subnet = get_subnet_fromdb(sub_id, user_id)
216
    original_dict = subnet_to_dict(original_subnet)
217

    
218
    if len(subnet) != 1:
219
        raise api.faults.BadRequest("Only the name of subnet can be updated")
220

    
221
    name = subnet.get("name", None)
222

    
223
    if not name:
224
        raise api.faults.BadRequest("Only the name of subnet can be updated")
225

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

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

    
262
    subnet_dict = subnet_to_dict(original_subnet)
263
    data = json.dumps({'subnet': subnet_dict})
264
    return HttpResponse(data, status=200)
265

    
266

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

    
281

    
282
def check_number_of_subnets(network, version):
283
    """Check if a user can add a subnet in a network"""
284
    if network.subnets.filter(ipversion=version):
285
        raise api.faults.BadRequest("Only one subnet of IPv4/IPv6 per "
286
                                    "network is allowed")
287

    
288

    
289
def check_dhcp_value(dhcp):
290
    """Check if dhcp value is in acceptable values"""
291
    if dhcp not in [True, False]:
292
        raise api.faults.BadRequest("Malformed request, enable_dhcp must be "
293
                                    "True or False")
294
    return dhcp
295

    
296

    
297
def check_name_length(name):
298
    """Check if the length of a name is within acceptable value"""
299
    if len(str(name)) > Subnet.SUBNET_NAME_LENGTH:
300
        raise api.faults.BadRequest("Subnet name too long")
301
    return name
302

    
303

    
304
def check_for_hosts_dns(subnet):
305
    """
306
    Check if a request contains host_routes or dns_nameservers options
307
    Expects the request in a dictionary format
308
    """
309
    if subnet.get('host_routes', None):
310
        raise api.faults.BadRequest("Setting host routes isn't supported")
311
    if subnet.get('dns_nameservers', None):
312
        raise api.faults.BadRequest("Setting dns nameservers isn't supported")
313

    
314

    
315
def get_subnet_fromdb(subnet_id, user_id, for_update=False):
316
    """
317
    Return a Subnet instance or raise ItemNotFound.
318
    This is the same as util.get_network
319
    """
320
    try:
321
        subnet_id = int(subnet_id)
322
        if for_update:
323
            return Subnet.objects.select_for_update().get(id=subnet_id,
324
                                                          network__userid=
325
                                                          user_id)
326
        return Subnet.objects.get(id=subnet_id, network__userid=user_id)
327
    except (ValueError, Subnet.DoesNotExist):
328
        raise api.faults.ItemNotFound('Subnet not found.')