Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (11.5 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
45

    
46
from ipaddr import IPv4Network, IPv6Network, IPv4Address, IPAddress, IPNetwork
47

    
48
log = getLogger(__name__)
49

    
50

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

    
60

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

    
65
    user_subnets = Subnet.objects.filter(network__userid=user_id)
66
    subnets_dict = [subnet_to_dict(sub)
67
                    for sub in user_subnets.order_by('id')]
68

    
69
    return subnets_dict
70

    
71

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

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

    
87
    # Returns the first available IP in the subnet
88
    if ipversion == 6:
89
        potential_gateway = str(IPv6Network(cidr).network + 1)
90
        check_number_of_subnets(network, 6)
91
    else:
92
        potential_gateway = str(IPv4Network(cidr).network + 1)
93
        check_number_of_subnets(network, 4)
94

    
95
    if ipversion == 6:
96
        networks.validate_network_params(None, None, cidr, gateway)
97
        if slac is not None:
98
            dhcp = check_boolean_value(slac, "enable_slac")
99
        else:
100
            dhcp = check_boolean_value(dhcp, "dhcp")
101
    else:
102
        networks.validate_network_params(cidr, gateway)
103
        dhcp = check_boolean_value(dhcp, "dhcp")
104

    
105
    name = check_name_length(name)
106

    
107
    gateway_ip = IPAddress(gateway)
108
    cidr_ip = IPNetwork(cidr)
109

    
110
    sub = Subnet.objects.create(name=name, network=network, cidr=cidr,
111
                                ipversion=ipversion, gateway=gateway,
112
                                dhcp=dhcp, host_routes=hosts,
113
                                dns_nameservers=dns)
114

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

    
132
    if pool_list:
133
        create_ip_pools(pool_list, cidr_ip, sub)
134

    
135
    return sub
136

    
137

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

    
146
    subnet_dict = subnet_to_dict(subnet)
147
    return subnet_dict
148

    
149

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

    
157

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

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

    
171
    check_name_length(name)
172

    
173
    subnet.name = name
174
    subnet.save()
175

    
176
    return subnet
177

    
178

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

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

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

    
207
    if subnet.ipversion == 6:
208
        dictionary['enable_slac'] = subnet.dhcp
209

    
210
    return dictionary
211

    
212

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

    
227

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

    
237

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

    
244

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

    
251

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

    
259

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

    
266

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

    
277

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

    
293

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

    
308

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

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

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