Statistics
| Branch: | Tag: | Revision:

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

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

    
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'^/detail(?:.json|.xml)?$', 'list_subnets', {'detail': True}),
56
    (r'^/([-\w]+)(?:/|.json|.xml)?$', 'subnet_demux'))
57

    
58

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

    
67

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

    
78

    
79
@api.api_method(http_method='GET', user_required=True, logger=log)
80
def list_subnets(request, detail=True):
81
    """List all subnets of a user"""
82
    userid = request.user_uniq
83
    subnets_list = Subnet.objects.filter(Q(public=True) |
84
                                         (Q(userid=userid) &
85
                                          Q(public=False)))\
86
                                 .order_by("id")
87
    subnets_list = subnets_list.prefetch_related("ip_pools")
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, user_id)
168

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

    
173

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

179
    """
180
    raise api.faults.BadRequest("Deletion of a subnet is not supported")
181

    
182

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

188
    """
189

    
190
    dictionary = utils.get_request_dict(request)
191
    user_id = request.user_uniq
192

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

    
198
    if len(subnet) != 1 or "name" not in subnet:
199
        raise api.faults.BadRequest("Only the name of a subnet can be updated")
200

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

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

    
207

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

    
214
    allocation_pools = [render_ip_pool(pool)
215
                        for pool in subnet.ip_pools.all()]
216

    
217
    d = {'id': str(subnet.id),
218
         'network_id': str(subnet.network_id),
219
         'name': subnet.name if subnet.name is not None else "",
220
         'tenant_id': subnet.userid,
221
         'user_id': subnet.userid,
222
         'public': subnet.public,
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': allocation_pools,
230
         'deleted': subnet.deleted}
231

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

    
235
    d['links'] = util.subnet_to_links(subnet.id)
236

    
237
    return d
238

    
239

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

    
246

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

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

    
264

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

    
271

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