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

from logging import getLogger
from snf_django.lib import api

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

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

46
import ipaddr
47
48
49
50

log = getLogger(__name__)


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


57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def demux(request):
    if request.method == 'GET':
        return list_subnets(request)
    elif request.method == 'POST':
        return create_subnet(request)
    else:
        return api.api_method_not_allowed(request)


def subnet_demux(request, sub_id):
    if request.method == 'GET':
        return get_subnet(request, sub_id)
    elif request.method == 'DELETE':
        return delete_subnet(request, sub_id)
    elif request.method == 'PUT':
        return update_subnet(request, sub_id)
    else:
        return api.api_method_not_allowed(request)


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

84
85
86
87
88
89
90
    data = json.dumps({'subnets': subnets_dict})

    return HttpResponse(data, status=200)


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

94
    """
95
96
97
98
99
100
101
102
103
104
105
    dictionary = utils.get_request_dict(request)
    log.info('create subnet %s', dictionary)

    try:
        subnet = dictionary['subnet']
        network_id = subnet['network_id']
        cidr = subnet['cidr']
    except KeyError:
        raise api.faults.BadRequest("Malformed request")

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

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

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

    if gateway is "":
        gateway = potential_gateway

125
    dhcp = subnet.get('enable_dhcp', True)
126
127
128
129
130
131
132
133
134
135
    slaac = subnet.get('enable_slaac', None)

    if ipversion == 6:
        if slaac is not None:
            dhcp = check_boolean_value(slaac, "enable_slaac")
        else:
            dhcp = check_boolean_value(dhcp, "dhcp")
    else:
        dhcp = check_boolean_value(dhcp, "dhcp")

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

139
140
141
142
143
144
    sub = subnets.create_subnet(network_id=network_id,
                                cidr=cidr,
                                name=name,
                                ipversion=ipversion,
                                gateway=gateway,
                                dhcp=dhcp,
145
                                slaac=slaac,
146
147
148
149
                                dns_nameservers=dns,
                                allocation_pools=allocation_pools,
                                host_routes=hosts,
                                user_id=request.user_uniq)
150
151
152
153
154
155
156
157
158
159

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


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

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

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


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

175
176
177
178
179
180
    """
    raise api.faults.BadRequest("Deletion of a subnet is not supported")


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

184
185
186
187
188
189
190
191
192
193
    """

    dictionary = utils.get_request_dict(request)
    user_id = request.user_uniq

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

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

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

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


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

    if allocation_pools:
        for pool in allocation_pools:
214
            cidr = ipaddr.IPNetwork(pool.base)
215
216
            start = str(cidr.network + pool.offset)
            end = str(cidr.network + pool.offset + pool.size - 1)
217
            pools.append({"start": start, "end": end})
218
219
220
221
222
223

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

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

235
    dictionary['links'] = util.subnet_to_links(subnet.id)
236
237
238
    return dictionary


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

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


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


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


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

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


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

291
292
293
    """
    pool_list = list()
    for pool in pools:
294
295
        parse = [pool["start"], pool["end"]]
        pool_list.append(parse)
296
    return pool_list
297
298
299
300
301
302
303
304


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