Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (10.7 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
import ipaddr
35
from logging import getLogger
36
from functools import wraps
37

    
38
from django.conf import settings
39
from django.db import transaction
40
from django.db.models import Q
41

    
42
from snf_django.lib import api
43
from snf_django.lib.api import faults
44
from synnefo.logic import utils
45

    
46
from synnefo.db.models import Subnet, Network, IPPoolTable
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 %s', user_id)
64

    
65
    query = (((Q(network__userid=user_id) & Q(network__public=False)) |
66
              Q(network__public=True)) & Q(deleted=False))
67
    user_subnets = Subnet.objects.filter(query)
68
    return user_subnets
69

    
70

    
71
@transaction.commit_on_success
72
def create_subnet(*args, **kwargs):
73
    return _create_subnet(*args, **kwargs)
74

    
75

    
76
def _create_subnet(network_id, user_id, cidr, name, ipversion=4, gateway=None,
77
                   dhcp=True, slaac=True, dns_nameservers=None,
78
                   allocation_pools=None, host_routes=None):
79
    """Create a subnet
80

81
    network_id and the desired cidr are mandatory, everything else is optional
82

83
    """
84
    try:
85
        network = Network.objects.get(id=network_id)
86
    except Network.DoesNotExist:
87
        raise api.faults.ItemNotFound("No network found with that id")
88

    
89
    if network.deleted:
90
        raise api.faults.BadRequest("Network has been deleted")
91

    
92
    if user_id != network.userid:
93
        raise api.faults.Unauthorized("Unauthorized operation")
94

    
95
    if ipversion not in [4, 6]:
96
        raise api.faults.BadRequest("Malformed IP version type")
97

    
98
    check_number_of_subnets(network, ipversion)
99

    
100
    if network.backend_networks.exists():
101
        raise api.faults.BadRequest("Cannot create subnet in network %s, VMs"
102
                                    " are already connected to this network" %
103
                                    network_id)
104

    
105
    try:
106
        cidr_ip = ipaddr.IPNetwork(cidr)
107
    except ValueError:
108
        raise api.faults.BadRequest("Malformed CIDR")
109

    
110
    if ipversion == 6:
111
        validate_subnet_params(subnet6=cidr, gateway6=gateway)
112
    else:
113
        validate_subnet_params(subnet=cidr, gateway=gateway)
114

    
115
    name = utils.check_name_length(name, Subnet.SUBNET_NAME_LENGTH, "Subnet "
116
                                   "name is too long")
117
    sub = Subnet.objects.create(name=name, network=network, cidr=cidr,
118
                                ipversion=ipversion, gateway=gateway,
119
                                dhcp=dhcp, host_routes=host_routes,
120
                                dns_nameservers=dns_nameservers)
121

    
122
    gateway_ip = ipaddr.IPAddress(gateway) if gateway else None
123

    
124
    if allocation_pools is not None:
125
        if ipversion == 6:
126
            raise api.faults.Conflict("Can't allocate an IP Pool in IPv6")
127
    elif ipversion == 4:
128
        # Check if the gateway is the first IP of the subnet, or the last. In
129
        # that case create a single ip pool.
130
        if gateway_ip:
131
            if int(gateway_ip) - int(cidr_ip) == 1:
132
                allocation_pools = [(gateway_ip + 1, cidr_ip.broadcast - 1)]
133
            elif int(cidr_ip.broadcast) - int(gateway_ip) == 1:
134
                allocation_pools = [(cidr_ip.network + 1, gateway_ip - 1)]
135
            else:
136
                # If the gateway isn't the first available ip, create two
137
                # different ip pools adjacent to said ip
138
                allocation_pools = [(cidr_ip.network + 1, gateway_ip - 1),
139
                                    (gateway_ip + 1, cidr_ip.broadcast - 1)]
140
        else:
141
            allocation_pools = [(cidr_ip.network + 1, cidr_ip.broadcast - 1)]
142

    
143
    if allocation_pools:
144
        # Validate the allocation pools
145
        validate_pools(allocation_pools, cidr_ip, gateway_ip)
146
        create_ip_pools(allocation_pools, cidr_ip, sub)
147

    
148
    return sub
149

    
150

    
151
def get_subnet(sub_id):
152
    """Show info of a specific subnet"""
153
    log.debug('get_subnet %s', sub_id)
154
    try:
155
        subnets = Subnet.objects
156
        return subnets.get(id=sub_id)
157
    except Subnet.DoesNotExist:
158
        raise api.faults.ItemNotFound("Subnet not found")
159

    
160

    
161
def delete_subnet():
162
    """Delete a subnet, raises BadRequest
163
    A subnet is deleted ONLY when the network that it belongs to is deleted
164

165
    """
166
    raise api.faults.BadRequest("Deletion of a subnet is not supported")
167

    
168

    
169
@transaction.commit_on_success
170
def update_subnet(sub_id, name, user_id):
171
    """Update the fields of a subnet
172
    Only the name can be updated
173

174
    """
175
    log.info('Update subnet %s, name %s' % (sub_id, name))
176

    
177
    try:
178
        subnet = Subnet.objects.get(id=sub_id)
179
    except:
180
        raise api.faults.ItemNotFound("Subnet not found")
181

    
182
    if user_id != subnet.network.userid:
183
        raise api.faults.Unauthorized("Unauthorized operation")
184

    
185
    utils.check_name_length(name, Subnet.SUBNET_NAME_LENGTH, "Subnet name is "
186
                            " too long")
187

    
188
    subnet.name = name
189
    subnet.save()
190

    
191
    return subnet
192

    
193

    
194
#Utility functions
195
def create_ip_pools(pools, cidr, subnet):
196
    """Create IP Pools in the database"""
197
    return [_create_ip_pool(pool, cidr, subnet) for pool in pools]
198

    
199

    
200
def _create_ip_pool(pool, cidr, subnet):
201
    size = int(pool[1]) - int(pool[0]) + 1
202
    base = str(cidr)
203
    offset = int(pool[0]) - int(cidr.network)
204
    return IPPoolTable.objects.create(size=size, offset=offset,
205
                                      base=base, subnet=subnet)
206

    
207

    
208
def check_number_of_subnets(network, version):
209
    """Check if a user can add a subnet in a network"""
210
    if network.subnets.filter(ipversion=version):
211
        raise api.faults.BadRequest("Only one subnet of IPv4/IPv6 per "
212
                                    "network is allowed")
213

    
214

    
215
def validate_pools(pool_list, cidr, gateway):
216
    """Validate IP Pools
217

218
    Validate the given IP pools are inside the cidr range
219
    Validate there are no overlaps in the given pools
220
    Finally, validate the gateway isn't in the given ip pools
221
    Input must be a list containing a sublist with start/end ranges as
222
    ipaddr.IPAddress items eg.,
223
    [[IPv4Address('192.168.42.11'), IPv4Address('192.168.42.15')],
224
     [IPv4Address('192.168.42.30'), IPv4Address('192.168.42.60')]]
225

226
    """
227
    if pool_list[0][0] <= cidr.network:
228
        raise api.faults.Conflict("IP Pool out of bounds")
229
    elif pool_list[-1][1] >= cidr.broadcast:
230
        raise api.faults.Conflict("IP Pool out of bounds")
231

    
232
    for start, end in pool_list:
233
        if start > end:
234
            raise api.faults.Conflict("Invalid IP pool range")
235
        # Raise BadRequest if gateway is inside the pool range
236
        if gateway:
237
            if not (gateway < start or gateway > end):
238
                raise api.faults.Conflict("Gateway cannot be in pool range")
239

    
240
    # Check if there is a conflict between the IP Pool ranges
241
    end = cidr.network
242
    for pool in pool_list:
243
        if end >= pool[0]:
244
            raise api.faults.Conflict("IP Pool range conflict")
245
        end = pool[1]
246

    
247

    
248
def validate_subnet_params(subnet=None, gateway=None, subnet6=None,
249
                           gateway6=None):
250
    if subnet:
251
        try:
252
            # Use strict option to not all subnets with host bits set
253
            network = ipaddr.IPv4Network(subnet, strict=True)
254
        except ValueError:
255
            raise faults.BadRequest("Invalid network IPv4 subnet")
256

    
257
        # Check that network size is allowed!
258
        prefixlen = network.prefixlen
259
        if prefixlen > 29 or prefixlen <= settings.MAX_CIDR_BLOCK:
260
            raise faults.OverLimit(
261
                message="Unsupported network size",
262
                details="Netmask must be in range: (%s, 29]" %
263
                settings.MAX_CIDR_BLOCK)
264
        if gateway:  # Check that gateway belongs to network
265
            try:
266
                gateway = ipaddr.IPv4Address(gateway)
267
            except ValueError:
268
                raise faults.BadRequest("Invalid network IPv4 gateway")
269
            if not gateway in network:
270
                raise faults.BadRequest("Invalid network IPv4 gateway")
271

    
272
    if subnet6:
273
        try:
274
            # Use strict option to not all subnets with host bits set
275
            network6 = ipaddr.IPv6Network(subnet6, strict=True)
276
        except ValueError:
277
            raise faults.BadRequest("Invalid network IPv6 subnet")
278
        # Check that network6 is an /64 subnet, because this is imposed by
279
        # 'mac2eui64' utiity.
280
        if network6.prefixlen != 64:
281
            msg = ("Unsupported IPv6 subnet size. Network netmask must be"
282
                   " /64")
283
            raise faults.BadRequest(msg)
284
        if gateway6:
285
            try:
286
                gateway6 = ipaddr.IPv6Address(gateway6)
287
            except ValueError:
288
                raise faults.BadRequest("Invalid network IPv6 gateway")
289
            if not gateway6 in network6:
290
                raise faults.BadRequest("Invalid network IPv6 gateway")
291

    
292

    
293
def parse_allocation_pools(allocation_pools):
294
    alloc = list()
295
    for pool in allocation_pools:
296
        try:
297
            start, end = pool.split(',')
298
            alloc.append([ipaddr.IPv4Address(start),
299
                          ipaddr.IPv4Address(end)])
300
        except ValueError:
301
            raise faults.BadRequest("Malformed IPv4 address")
302

    
303
    return alloc