Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / subnets.py @ 99718617

History | View | Annotate | Download (11.6 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
from django.db import transaction
38

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

    
43
from snf_django.lib.api import utils
44
from synnefo.db.models import Subnet, Network, IPPoolTable
45
from synnefo.logic import networks
46

    
47
from ipaddr import IPv4Address, IPAddress, IPNetwork
48

    
49
log = getLogger(__name__)
50

    
51

    
52
def subnet_command(action):
53
    def decorator(func):
54
        @wraps(func)
55
        @transaction.commit_on_success()
56
        def wrapper(subnet, *args, **kwargs):
57
            return func(subnet, *args, **kwargs)
58
        return wrapper
59
    return decorator
60

    
61

    
62
def list_subnets(user_id):
63
    """List all subnets of a user"""
64
    log.debug('list_subnets')
65

    
66
    user_subnets = Subnet.objects.filter(network__userid=user_id)
67
    return user_subnets
68

    
69

    
70
@transaction.commit_on_success
71
def create_subnet(network_id, cidr, name, ipversion, gateway, dhcp, slac,
72
                  dns_nameservers, allocation_pools, host_routes, user_id):
73
    """
74
    Create a subnet
75
    network_id and the desired cidr are mandatory, everything else is optional
76
    """
77
    try:
78
        network = Network.objects.get(id=network_id)
79
    except Network.DoesNotExist:
80
        raise api.faults.ItemNotFound("No networks found with that id")
81

    
82
    if user_id != network.userid:
83
        raise api.faults.Unauthorized("Unauthorized operation")
84

    
85
    if ipversion not in [4, 6]:
86
        raise api.faults.BadRequest("Malformed IP version type")
87

    
88
    check_number_of_subnets(network, ipversion)
89

    
90
    # Returns the first available IP in the subnet
91
    try:
92
        cidr_ip = IPNetwork(cidr)
93
    except ValueError:
94
        raise api.faults.BadRequest("Malformed CIDR")
95
    potential_gateway = str(IPNetwork(cidr).network + 1)
96

    
97
    if gateway is "":
98
        gateway = potential_gateway
99

    
100
    if ipversion == 6:
101
        networks.validate_network_params(None, None, cidr, gateway)
102
        if slac is not None:
103
            dhcp = check_boolean_value(slac, "enable_slac")
104
        else:
105
            dhcp = check_boolean_value(dhcp, "dhcp")
106
    else:
107
        networks.validate_network_params(cidr, gateway)
108
        dhcp = check_boolean_value(dhcp, "dhcp")
109

    
110
    name = check_name_length(name)
111

    
112
    gateway_ip = IPAddress(gateway)
113

    
114
    sub = Subnet.objects.create(name=name, network=network, cidr=cidr,
115
                                ipversion=ipversion, gateway=gateway,
116
                                dhcp=dhcp, host_routes=host_routes,
117
                                dns_nameservers=dns_nameservers)
118

    
119
    if allocation_pools is not None:
120
        # If the user specified IP allocation pools, validate them and use them
121
        if ipversion == 6:
122
            raise api.faults.Conflict("Can't allocate an IP Pool in IPv6")
123
        validate_subpools(allocation_pools, cidr_ip, gateway_ip)
124
    if allocation_pools is None and ipversion == 4:
125
        # Check if the gateway is the first IP of the subnet, in this case
126
        # create a single ip pool
127
        if int(gateway_ip) - int(cidr_ip) == 1:
128
            allocation_pools = [[gateway_ip + 1, cidr_ip.broadcast - 1]]
129
        else:
130
            # If the gateway isn't the first available ip, create two different
131
            # ip pools adjacent to said ip
132
            allocation_pools.append([cidr_ip.network + 1, gateway_ip - 1])
133
            allocation_pools.append([gateway_ip + 1, cidr_ip.broadcast - 1])
134

    
135
    if allocation_pools:
136
        create_ip_pools(allocation_pools, cidr_ip, sub)
137

    
138
    return sub
139

    
140

    
141
def get_subnet(sub_id):
142
    """Show info of a specific subnet"""
143
    log.debug('get_subnet %s', sub_id)
144
    try:
145
        subnet = Subnet.objects.get(id=sub_id)
146
    except Subnet.DoesNotExist:
147
        raise api.faults.ItemNotFound("Subnet not found")
148

    
149
    return subnet
150

    
151

    
152
def delete_subnet():
153
    """
154
    Delete a subnet, raises BadRequest
155
    A subnet is deleted ONLY when the network that it belongs to is deleted
156
    """
157
    raise api.faults.BadRequest("Deletion of a subnet is not supported")
158

    
159

    
160
@transaction.commit_on_success
161
def update_subnet(sub_id, name):
162
    """
163
    Update the fields of a subnet
164
    Only the name can be updated
165
    """
166
    log.info('Update subnet %s, name %s' % (sub_id, name))
167

    
168
    try:
169
        subnet = Subnet.objects.get(id=sub_id)
170
    except:
171
        raise api.faults.ItemNotFound("Subnet not found")
172

    
173
    check_name_length(name)
174

    
175
    subnet.name = name
176
    subnet.save()
177

    
178
    return subnet
179

    
180

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

    
189
    if allocation_pools:
190
        for pool in allocation_pools:
191
            cidr = IPNetwork(pool.base)
192
            start = str(cidr.network + pool.offset)
193
            end = str(cidr.network + pool.offset + pool.size - 1)
194
            pools.append({"start": start, "end": end})
195

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

    
209
    if subnet.ipversion == 6:
210
        dictionary['enable_slac'] = subnet.dhcp
211

    
212
    return dictionary
213

    
214

    
215
def string_to_ipaddr(pools):
216
    """
217
    Convert [["192.168.42.1", "192.168.42.15"],
218
            ["192.168.42.30", "192.168.42.60"]]
219
    to
220
            [[IPv4Address('192.168.42.1'), IPv4Address('192.168.42.15')],
221
            [IPv4Address('192.168.42.30'), IPv4Address('192.168.42.60')]]
222
    and sort the output
223
    """
224
    pool_list = [(map(lambda ip_str: IPAddress(ip_str), pool))
225
                 for pool in pools]
226
    pool_list.sort()
227
    return pool_list
228

    
229

    
230
def create_ip_pools(pools, cidr, subnet):
231
    """Create IP Pools in the database"""
232
    for pool in pools:
233
        size = int(pool[1]) - int(pool[0]) + 1
234
        base = str(cidr)
235
        offset = int(pool[0]) - int(cidr.network)
236
        ip_pool = IPPoolTable.objects.create(size=size, offset=offset,
237
                                             base=base, subnet=subnet)
238

    
239

    
240
def check_empty_lists(value):
241
    """Check if value is Null/None, in which case we return an empty list"""
242
    if value is None:
243
        return []
244
    return value
245

    
246

    
247
def check_number_of_subnets(network, version):
248
    """Check if a user can add a subnet in a network"""
249
    if network.subnets.filter(ipversion=version):
250
        raise api.faults.BadRequest("Only one subnet of IPv4/IPv6 per "
251
                                    "network is allowed")
252

    
253

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

    
261

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

    
268

    
269
def check_for_hosts_dns(subnet):
270
    """
271
    Check if a request contains host_routes or dns_nameservers options
272
    Expects the request in a dictionary format
273
    """
274
    if subnet.get('host_routes', None):
275
        raise api.faults.BadRequest("Setting host routes isn't supported")
276
    if subnet.get('dns_nameservers', None):
277
        raise api.faults.BadRequest("Setting dns nameservers isn't supported")
278

    
279

    
280
def get_subnet_fromdb(subnet_id, user_id, for_update=False):
281
    """
282
    Return a Subnet instance or raise ItemNotFound.
283
    This is the same as util.get_network
284
    """
285
    try:
286
        subnet_id = int(subnet_id)
287
        if for_update:
288
            return Subnet.objects.select_for_update().get(id=subnet_id,
289
                                                          network__userid=
290
                                                          user_id)
291
        return Subnet.objects.get(id=subnet_id, network__userid=user_id)
292
    except (ValueError, Subnet.DoesNotExist):
293
        raise api.faults.ItemNotFound('Subnet not found')
294

    
295

    
296
def parse_ip_pools(pools):
297
    """
298
    Convert [{'start': '192.168.42.1', 'end': '192.168.42.15'},
299
             {'start': '192.168.42.30', 'end': '192.168.42.60'}]
300
    to
301
            [["192.168.42.1", "192.168.42.15"],
302
             ["192.168.42.30", "192.168.42.60"]]
303
    """
304
    pool_list = list()
305
    for pool in pools:
306
        parse = [pool["start"], pool["end"]]
307
        pool_list.append(parse)
308
    return pool_list
309

    
310

    
311
def validate_subpools(pool_list, cidr, gateway):
312
    """
313
    Validate the given IP pools are inside the cidr range
314
    Validate there are no overlaps in the given pools
315
    Finally, validate the gateway isn't in the given ip pools
316
    Input must be a list containing a sublist with start/end ranges as
317
    ipaddr.IPAddress items eg.,
318
    [[IPv4Address('192.168.42.11'), IPv4Address('192.168.42.15')],
319
     [IPv4Address('192.168.42.30'), IPv4Address('192.168.42.60')]]
320
    """
321
    if pool_list[0][0] <= cidr.network:
322
        raise api.faults.Conflict("IP Pool out of bounds")
323
    elif pool_list[-1][1] >= cidr.broadcast:
324
        raise api.faults.Conflict("IP Pool out of bounds")
325

    
326
    for start, end in pool_list:
327
        if start > end:
328
            raise api.faults.Conflict("Invalid IP pool range")
329
        # Raise BadRequest if gateway is inside the pool range
330
        if not (gateway < start or gateway > end):
331
            raise api.faults.Conflict("Gateway cannot be in pool range")
332

    
333
    # Check if there is a conflict between the IP Poll ranges
334
    end = cidr.network
335
    for pool in pool_list:
336
        if end >= pool[0]:
337
            raise api.faults.Conflict("IP Pool range conflict")
338
        end = pool[1]