Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (10.1 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

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

    
45
import ipaddr
46

    
47
log = getLogger(__name__)
48

    
49

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

    
55

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

    
64

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

    
75

    
76
@api.api_method(http_method='GET', user_required=True, logger=log)
77
def list_subnets(request):
78
    """List all subnets of a user"""
79
    subnet_list = subnets.list_subnets(request.user_uniq)
80
    subnets_dict = [subnet_to_dict(sub)
81
                    for sub in subnet_list.order_by('id')]
82

    
83
    data = json.dumps({'subnets': subnets_dict})
84

    
85
    return HttpResponse(data, status=200)
86

    
87

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

93
    """
94
    dictionary = utils.get_request_dict(request)
95
    log.info('create subnet %s', dictionary)
96

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

    
104
    allocation_pools = subnet.get('allocation_pools', None)
105
    if allocation_pools is not None:
106
        pool = parse_ip_pools(allocation_pools)
107
        allocation_pools = string_to_ipaddr(pool)
108

    
109
    name = subnet.get('name', None)
110
    ipversion = subnet.get('ip_version', 4)
111

    
112
    # If no gateway is specified, send an empty string, because None is used
113
    # if the user wants no gateway at all
114
    gateway = subnet.get('gateway_ip', "")
115
    try:
116
        cidr_ip = ipaddr.IPNetwork(cidr)
117
    except ValueError:
118
        raise api.faults.BadRequest("Malformed CIDR")
119
    potential_gateway = str(ipaddr.IPNetwork(cidr).network + 1)
120

    
121
    if gateway is "":
122
        gateway = potential_gateway
123

    
124
    dhcp = subnet.get('enable_dhcp', True)
125
    slaac = subnet.get('enable_slaac', None)
126

    
127
    if ipversion == 6:
128
        if slaac is not None:
129
            dhcp = check_boolean_value(slaac, "enable_slaac")
130
        else:
131
            dhcp = check_boolean_value(dhcp, "dhcp")
132
    else:
133
        dhcp = check_boolean_value(dhcp, "dhcp")
134

    
135
    dns = subnet.get('dns_nameservers', None)
136
    hosts = subnet.get('host_routes', None)
137

    
138
    sub = subnets.create_subnet(network_id=network_id,
139
                                cidr=cidr,
140
                                name=name,
141
                                ipversion=ipversion,
142
                                gateway=gateway,
143
                                dhcp=dhcp,
144
                                slaac=slaac,
145
                                dns_nameservers=dns,
146
                                allocation_pools=allocation_pools,
147
                                host_routes=hosts,
148
                                user_id=request.user_uniq)
149

    
150
    subnet_dict = subnet_to_dict(sub)
151
    data = json.dumps({'subnet': subnet_dict})
152
    return HttpResponse(data, status=200)
153

    
154

    
155
@api.api_method(http_method='GET', user_required=True, logger=log)
156
def get_subnet(request, sub_id):
157
    """Show info of a specific subnet"""
158
    user_id = request.user_uniq
159
    subnet = subnets.get_subnet(sub_id)
160

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

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

    
168

    
169
@api.api_method(http_method='DELETE', user_required=True, logger=log)
170
def delete_subnet(request, sub_id):
171
    """Delete a subnet, raises BadRequest
172
    A subnet is deleted ONLY when the network that it belongs to is deleted
173

174
    """
175
    raise api.faults.BadRequest("Deletion of a subnet is not supported")
176

    
177

    
178
@api.api_method(http_method='PUT', user_required=True, logger=log)
179
def update_subnet(request, sub_id):
180
    """Update the fields of a subnet
181
    Only the name can be updated, everything else returns BadRequest
182

183
    """
184

    
185
    dictionary = utils.get_request_dict(request)
186
    user_id = request.user_uniq
187

    
188
    try:
189
        subnet = dictionary['subnet']
190
    except KeyError:
191
        raise api.faults.BadRequest("Malformed request")
192

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

    
196
    name = subnet.get("name", None)
197

    
198
    subnet_dict = subnet_to_dict(subnets.update_subnet(sub_id, name, user_id))
199
    data = json.dumps({'subnet': subnet_dict})
200
    return HttpResponse(data, status=200)
201

    
202

    
203
#Utility functions
204
def subnet_to_dict(subnet):
205
    """Returns a dictionary containing the info of a subnet"""
206
    dns = check_empty_lists(subnet.dns_nameservers)
207
    hosts = check_empty_lists(subnet.host_routes)
208
    allocation_pools = subnet.ip_pools.all()
209
    pools = list()
210

    
211
    if allocation_pools:
212
        for pool in allocation_pools:
213
            cidr = ipaddr.IPNetwork(pool.base)
214
            start = str(cidr.network + pool.offset)
215
            end = str(cidr.network + pool.offset + pool.size - 1)
216
            pools.append({"start": start, "end": end})
217

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

    
231
    if subnet.ipversion == 6:
232
        dictionary['enable_slaac'] = subnet.dhcp
233

    
234
    return dictionary
235

    
236

    
237
def string_to_ipaddr(pools):
238
    """Convert [["192.168.42.1", "192.168.42.15"],
239
                ["192.168.42.30", "192.168.42.60"]]
240
    to
241
                [[IPv4Address('192.168.42.1'), IPv4Address('192.168.42.15')],
242
                [IPv4Address('192.168.42.30'), IPv4Address('192.168.42.60')]]
243
    and sort the output
244

245
    """
246
    pool_list = [(map(lambda ip_str: ipaddr.IPAddress(ip_str), pool))
247
                 for pool in pools]
248
    pool_list.sort()
249
    return pool_list
250

    
251

    
252
def check_empty_lists(value):
253
    """Check if value is Null/None, in which case we return an empty list"""
254
    if value is None:
255
        return []
256
    return value
257

    
258

    
259
def check_name_length(name):
260
    """Check if the length of a name is within acceptable value"""
261
    if len(str(name)) > Subnet.SUBNET_NAME_LENGTH:
262
        raise api.faults.BadRequest("Subnet name too long")
263
    return name
264

    
265

    
266
def get_subnet_fromdb(subnet_id, user_id, for_update=False):
267
    """Return a Subnet instance or raise ItemNotFound.
268
    This is the same as util.get_network
269

270
    """
271
    try:
272
        subnet_id = int(subnet_id)
273
        if for_update:
274
            return Subnet.objects.select_for_update().get(id=subnet_id,
275
                                                          network__userid=
276
                                                          user_id)
277
        return Subnet.objects.get(id=subnet_id, network__userid=user_id)
278
    except (ValueError, Subnet.DoesNotExist):
279
        raise api.faults.ItemNotFound('Subnet not found')
280

    
281

    
282
def parse_ip_pools(pools):
283
    """Convert [{'start': '192.168.42.1', 'end': '192.168.42.15'},
284
             {'start': '192.168.42.30', 'end': '192.168.42.60'}]
285
    to
286
            [["192.168.42.1", "192.168.42.15"],
287
             ["192.168.42.30", "192.168.42.60"]]
288

289
    """
290
    pool_list = list()
291
    for pool in pools:
292
        parse = [pool["start"], pool["end"]]
293
        pool_list.append(parse)
294
    return pool_list
295

    
296

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