Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (9.4 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, IPPoolTable
44
from synnefo.logic import networks, subnets
45

    
46
from ipaddr import IPv4Network, IPv6Network, IPv4Address, IPAddress, IPNetwork
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
    allocation_pools = subnet.get('allocation_pools', None)
106
    if allocation_pools is not None:
107
        pool = parse_ip_pools(allocation_pools)
108
        allocation_pools = string_to_ipaddr(pool)
109

    
110
    name = subnet.get('name', None)
111
    ipversion = subnet.get('ip_version', 4)
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
    dhcp = subnet.get('enable_dhcp', True)
116
    slac = subnet.get('enable_slac', None)
117
    dns = subnet.get('dns_nameservers', None)
118
    hosts = subnet.get('host_routes', None)
119

    
120
    sub = subnets.create_subnet(network_id=network_id,
121
                                cidr=cidr,
122
                                name=name,
123
                                ipversion=ipversion,
124
                                gateway=gateway,
125
                                dhcp=dhcp,
126
                                slac=slac,
127
                                dns_nameservers=dns,
128
                                allocation_pools=allocation_pools,
129
                                host_routes=hosts,
130
                                user_id=request.user_uniq)
131

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

    
136

    
137
@api.api_method(http_method='GET', user_required=True, logger=log)
138
def get_subnet(request, sub_id):
139
    """Show info of a specific subnet"""
140
    user_id = request.user_uniq
141
    subnet = subnets.get_subnet(sub_id)
142

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

    
146
    subnet_dict = subnet_to_dict(subnet)
147
    data = json.dumps({'subnet': subnet_dict})
148
    return HttpResponse(data, status=200)
149

    
150

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

156
    """
157
    raise api.faults.BadRequest("Deletion of a subnet is not supported")
158

    
159

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

165
    """
166

    
167
    dictionary = utils.get_request_dict(request)
168
    user_id = request.user_uniq
169

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

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

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

    
180
    subnet_dict = subnet_to_dict(subnets.update_subnet(sub_id, name))
181
    data = json.dumps({'subnet': subnet_dict})
182
    return HttpResponse(data, status=200)
183

    
184

    
185
#Utility functions
186
def subnet_to_dict(subnet):
187
    """Returns a dictionary containing the info of a subnet"""
188
    dns = check_empty_lists(subnet.dns_nameservers)
189
    hosts = check_empty_lists(subnet.host_routes)
190
    allocation_pools = subnet.ip_pools.all()
191
    pools = list()
192

    
193
    if allocation_pools:
194
        for pool in allocation_pools:
195
            cidr = IPNetwork(pool.base)
196
            start = str(cidr.network + pool.offset)
197
            end = str(cidr.network + pool.offset + pool.size - 1)
198
            pools.append({"start": start, "end": end})
199

    
200
    dictionary = dict({'id': str(subnet.id),
201
                       'network_id': str(subnet.network.id),
202
                       'name': subnet.name if subnet.name is not None else "",
203
                       'tenant_id': subnet.network.userid,
204
                       'user_id': subnet.network.userid,
205
                       'gateway_ip': subnet.gateway,
206
                       'ip_version': subnet.ipversion,
207
                       'cidr': subnet.cidr,
208
                       'enable_dhcp': subnet.dhcp,
209
                       'dns_nameservers': dns,
210
                       'host_routes': hosts,
211
                       'allocation_pools': pools if pools is not None else []})
212

    
213
    if subnet.ipversion == 6:
214
        dictionary['enable_slac'] = subnet.dhcp
215

    
216
    return dictionary
217

    
218

    
219
def string_to_ipaddr(pools):
220
    """Convert [["192.168.42.1", "192.168.42.15"],
221
                ["192.168.42.30", "192.168.42.60"]]
222
    to
223
                [[IPv4Address('192.168.42.1'), IPv4Address('192.168.42.15')],
224
                [IPv4Address('192.168.42.30'), IPv4Address('192.168.42.60')]]
225
    and sort the output
226

227
    """
228
    pool_list = [(map(lambda ip_str: IPAddress(ip_str), pool))
229
                 for pool in pools]
230
    pool_list.sort()
231
    return pool_list
232

    
233

    
234
def check_empty_lists(value):
235
    """Check if value is Null/None, in which case we return an empty list"""
236
    if value is None:
237
        return []
238
    return value
239

    
240

    
241
def check_name_length(name):
242
    """Check if the length of a name is within acceptable value"""
243
    if len(str(name)) > Subnet.SUBNET_NAME_LENGTH:
244
        raise api.faults.BadRequest("Subnet name too long")
245
    return name
246

    
247

    
248
def get_subnet_fromdb(subnet_id, user_id, for_update=False):
249
    """Return a Subnet instance or raise ItemNotFound.
250
    This is the same as util.get_network
251

252
    """
253
    try:
254
        subnet_id = int(subnet_id)
255
        if for_update:
256
            return Subnet.objects.select_for_update().get(id=subnet_id,
257
                                                          network__userid=
258
                                                          user_id)
259
        return Subnet.objects.get(id=subnet_id, network__userid=user_id)
260
    except (ValueError, Subnet.DoesNotExist):
261
        raise api.faults.ItemNotFound('Subnet not found')
262

    
263

    
264
def parse_ip_pools(pools):
265
    """Convert [{'start': '192.168.42.1', 'end': '192.168.42.15'},
266
             {'start': '192.168.42.30', 'end': '192.168.42.60'}]
267
    to
268
            [["192.168.42.1", "192.168.42.15"],
269
             ["192.168.42.30", "192.168.42.60"]]
270

271
    """
272
    pool_list = list()
273
    for pool in pools:
274
        parse = [pool["start"], pool["end"]]
275
        pool_list.append(parse)
276
    return pool_list