Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (8.8 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
from synnefo.api import util
45

    
46
import ipaddr
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
    subnet_list = subnets.list_subnets(request.user_uniq)
81
    subnets_dict = [subnet_to_dict(sub)
82
                    for sub in subnet_list.order_by('id')]
83

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

    
86
    return HttpResponse(data, status=200)
87

    
88

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

94
    """
95
    dictionary = utils.get_request_dict(request)
96
    log.info('create subnet %s', dictionary)
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
    name = subnet.get('name', None)
106
    ipversion = subnet.get('ip_version', 4)
107

    
108
    allocation_pools = subnet.get('allocation_pools', None)
109
    if allocation_pools is not None:
110
        allocation_pools = parse_ip_pools(allocation_pools)
111

    
112
    try:
113
        cidr_ip = ipaddr.IPNetwork(cidr)
114
    except ValueError:
115
        raise api.faults.BadRequest("Malformed CIDR '%s'" % cidr)
116

    
117
    # If no gateway is specified, send an empty string, because None is used
118
    # if the user wants no gateway at all
119
    gateway = subnet.get('gateway_ip', "")
120
    if gateway is "":
121
        gateway = str(cidr_ip.network + 1)
122

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

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

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

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

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

    
153

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

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

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

    
167

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

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

    
176

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

182
    """
183

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

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

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

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

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

    
201

    
202
#Utility functions
203
def subnet_to_dict(subnet):
204
    """Returns a dictionary containing the info of a subnet"""
205
    dns = check_empty_lists(subnet.dns_nameservers)
206
    hosts = check_empty_lists(subnet.host_routes)
207

    
208
    allocation_pools = [render_ip_pool(pool)
209
                        for pool in subnet.ip_pools.all()]
210

    
211
    network = subnet.network
212
    d = {'id': str(subnet.id),
213
         'network_id': str(network.id),
214
         'name': subnet.name if subnet.name is not None else "",
215
         'tenant_id': network.userid,
216
         'user_id': network.userid,
217
         'gateway_ip': subnet.gateway,
218
         'ip_version': subnet.ipversion,
219
         'cidr': subnet.cidr,
220
         'enable_dhcp': subnet.dhcp,
221
         'dns_nameservers': dns,
222
         'host_routes': hosts,
223
         'allocation_pools': allocation_pools}
224

    
225
    if subnet.ipversion == 6:
226
        d['enable_slaac'] = subnet.dhcp
227

    
228
    d['links'] = util.subnet_to_links(subnet.id)
229

    
230
    return d
231

    
232

    
233
def render_ip_pool(pool):
234
    network = ipaddr.IPNetwork(pool.base).network
235
    start = str(network + pool.offset)
236
    end = str(network + pool.offset + pool.size - 1)
237
    return {"start": start, "end": end}
238

    
239

    
240
def parse_ip_pools(pools):
241
    """Convert [{'start': '192.168.42.1', 'end': '192.168.42.15'},
242
             {'start': '192.168.42.30', 'end': '192.168.42.60'}]
243
    to
244
            [(IPv4Address("192.168.42.1"), IPv4Address("192.168.42.15")),
245
             (IPv4Address("192.168.42.30"), IPv4Address("192.168.42.60"))]
246

247
    """
248
    try:
249
        return sorted([(ipaddr.IPv4Address(p["start"]),
250
                        ipaddr.IPv4Address(p["end"])) for p in pools])
251
    except KeyError:
252
        raise api.faults.BadRequest("Malformed allocation pool.")
253
    except ipaddr.AddressValueError:
254
        raise api.faults.BadRequest("Allocation pools contain invalid IPv4"
255
                                    " address")
256

    
257

    
258
def check_empty_lists(value):
259
    """Check if value is Null/None, in which case we return an empty list"""
260
    if value is None:
261
        return []
262
    return value
263

    
264

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